diff --git a/.appveyor.yml b/.appveyor.yml index 07d369cf..1ac073c4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,24 +4,62 @@ shallow_clone: true -image: Visual Studio 2015 +image: Visual Studio 2017 branches: only: - master - develop +install: + - cd .. + - git clone -b %APPVEYOR_REPO_BRANCH% https://github.com/boostorg/boost + - cd boost + - mkdir libs\histogram + - xcopy /s /e /q %APPVEYOR_BUILD_FOLDER% libs\histogram + - git submodule init libs/array + - git submodule init libs/assert + - git submodule init libs/bind + - git submodule init libs/callable_traits + - git submodule init libs/concept_check + - git submodule init libs/config + - git submodule init libs/container + - git submodule init libs/container_hash + - git submodule init libs/core + - git submodule init libs/detail + - git submodule init libs/function + - git submodule init libs/integer + - git submodule init libs/io + - git submodule init libs/iterator + - git submodule init libs/lexical_cast + - git submodule init libs/math + - git submodule init libs/move + - git submodule init libs/mp11 + - git submodule init libs/mpl + - git submodule init libs/multiprecision + - git submodule init libs/numeric + - git submodule init libs/optional + - git submodule init libs/predef + - git submodule init libs/preprocessor + - git submodule init libs/range + - git submodule init libs/rational + - git submodule init libs/serialization + - git submodule init libs/smart_ptr + - git submodule init libs/spirit + - git submodule init libs/static_assert + - git submodule init libs/throw_exception + - git submodule init libs/type_index + - git submodule init libs/type_traits + - git submodule init libs/typeof + - git submodule init libs/units + - git submodule init libs/utility + - git submodule init libs/variant + - git submodule init tools/build + - git submodule update + - bootstrap + - b2 --with-serialization + build: off -environment: - # MSVC_DEFAULT_OPTIONS: ON - BOOST_ROOT: C:\Libraries\boost_1_67_0 - test_script: - # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - - mkdir build - - cd build - - cmake .. - -DBOOST_ROOT="%BOOST_ROOT%" -DBoost_USE_STATIC_LIBS="ON" - - cmake --build . - - ctest -V + - b2 libs/histogram diff --git a/.gitignore b/.gitignore index ce5e62b5..37d22078 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *build* +histogram.sublime-workspace CMakeSettings.json doc/html/.buildinfo doc/html/.doctrees diff --git a/.travis.yml b/.travis.yml index ff9fb51a..94fc9323 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,26 +15,40 @@ branches: - master - develop +# g++-5 also needed for clang to see C++14 stdlib +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - libstdc++-5-dev libgcc-5-dev libstdc++6 libasan2 libtsan0 libubsan0 + matrix: include: - os: linux - compiler: gcc + compiler: clang env: SERIAL=OFF - - os: linux - compiler: gcc - env: SERIAL=ON - os: linux compiler: clang env: SERIAL=ON - os: osx env: SERIAL=OFF - os: linux # coverage build - compiler: gcc - env: GCOV=gcov + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-5 + env: + - MATRIX_EVAL="GCOV=gcov-5 && CC=gcc-5 && CXX=g++-5" git: depth: 1 +before_install: + - eval "${MATRIX_EVAL}" + # Install packages (pre-installed: pytest) install: - source tools/travis_install_boost.sh @@ -67,7 +81,7 @@ after_success: coveralls -r .. -b . --verbose --exclude ${TRAVIS_BUILD_DIR}/deps --gcov=`which ${GCOV}` --gcov-options '\-lpbc'; fi -after_failure: +# after_failure: # - otool -L histogram.so # - printf "r\nbt" > gdb.cmd # - for x in *_test; do diff --git a/CMakeLists.txt b/CMakeLists.txt index a061894e..11cd910b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,30 +8,18 @@ cmake_minimum_required (VERSION 3.5) project(histogram CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(MIN_BOOST_VERSION 1.66) +if ("$ENV{BOOST_ROOT}" STREQUAL "") + set(BOOST_INCLUDEDIR "${PROJECT_SOURCE_DIR}/../..") +endif() + # setup build option(BUILD_BENCHMARKS "Build benchmarks" OFF) option(TEST_SERIALIZATION "Test serialization code" OFF) -mark_as_advanced(BUILD_BENCHMARKS) -mark_as_advanced(TEST_SERIALIZATION) - -# set build type if none is specified -set(default_build_type "Release") -if (EXISTS "${PROJECT_SOURCE_DIR}/.git") - set(default_build_type "Debug") -endif() -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to '${default_build_type}' as none was specified.") - set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE - STRING "Choose the type of build." FORCE) - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Release" "MinSizeRel" "RelWithDebInfo") -endif() # serialization is optional if (TEST_SERIALIZATION) @@ -57,7 +45,7 @@ function(compiled_test SRC) target_include_directories(${BASENAME} PUBLIC include ${Boost_INCLUDE_DIR}) if(CMAKE_BUILD_TYPE MATCHES coverage) - target_compile_options(${BASENAME} PRIVATE -O0 -g --coverage) + target_compile_options(${BASENAME} PRIVATE --coverage) target_link_libraries(${BASENAME} PRIVATE --coverage) endif() @@ -65,24 +53,24 @@ function(compiled_test SRC) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_options(${BASENAME} PRIVATE -D_SCL_SECURE_NO_WARNINGS) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options(${BASENAME} PRIVATE -Wall -Wextra) + target_compile_options(${BASENAME} PRIVATE -Wall -Wextra -g -O0) if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6) # -fpermissive needed for cpp_int in gcc-6 - target_compile_options(${BASENAME} PRIVATE -fpermissive) + # -Wno-noexcept-type needed for callable_traits + target_compile_options(${BASENAME} PRIVATE -fpermissive -Wno-noexcept-type) endif() elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - target_compile_options(${BASENAME} PRIVATE -Wall -Wextra + target_compile_options(${BASENAME} PRIVATE -Wall -Wextra -g -O0 -Wno-unused-local-typedef -D__STRICT_ANSI__) endif() - ## deactivate sanitizers for now, LeakSanitizer is crashing - # # activate sanitizers for clang builds - # if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # target_compile_options(${BASENAME} PRIVATE - # -fsanitize=address,undefined - # -fno-omit-frame-pointer) - # target_link_libraries(${BASENAME} PRIVATE -fsanitize=address,undefined) - # endif() + # activate sanitizers for clang builds + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${BASENAME} PRIVATE + -fsanitize=address,undefined + -fno-omit-frame-pointer) + target_link_libraries(${BASENAME} PRIVATE -fsanitize=address,undefined) + endif() if (${BASENAME} MATCHES "_fail") add_test(${BASENAME} python ${PROJECT_SOURCE_DIR}/tools/pass_on_fail.py ${BASENAME}) @@ -93,9 +81,13 @@ endfunction() compiled_test(test/adaptive_storage_test.cpp) compiled_test(test/array_storage_test.cpp) -compiled_test(test/axis_test.cpp) +compiled_test(test/axis_regular_test.cpp) +compiled_test(test/axis_circular_test.cpp) +compiled_test(test/axis_variable_test.cpp) +compiled_test(test/axis_integer_test.cpp) +compiled_test(test/axis_category_test.cpp) +compiled_test(test/axis_variant_test.cpp) compiled_test(test/detail_test.cpp) -compiled_test(test/histogram_dynamic_add_fail.cpp) compiled_test(test/histogram_dynamic_fill_one_dimensional_vector_fail.cpp) compiled_test(test/histogram_dynamic_fill_one_dimensional_tuple_fail.cpp) compiled_test(test/histogram_dynamic_at_tuple_wrong_dimension_fail.cpp) @@ -103,7 +95,6 @@ compiled_test(test/histogram_dynamic_at_vector_wrong_dimension_fail.cpp) compiled_test(test/histogram_dynamic_at_wrong_dimension_fail.cpp) compiled_test(test/histogram_dynamic_reduce_wrong_order_fail.cpp) # test fail to compile (test/histogram_static_fill_one_dimensional_tuple_fail.cpp) -compiled_test(test/histogram_static_add_fail.cpp) compiled_test(test/histogram_static_at_vector_wrong_dimension_fail.cpp) compiled_test(test/histogram_dynamic_test.cpp) compiled_test(test/histogram_mixed_test.cpp) @@ -118,7 +109,8 @@ compiled_test(examples/getting_started_listing_02.cpp) compiled_test(examples/guide_access_bin_counts.cpp) compiled_test(examples/guide_axis_with_labels.cpp) compiled_test(examples/guide_axis_with_uoflow_off.cpp) -compiled_test(examples/guide_custom_axis.cpp) +compiled_test(examples/guide_custom_modified_axis.cpp) +compiled_test(examples/guide_custom_minimal_axis.cpp) compiled_test(examples/guide_custom_storage.cpp) compiled_test(examples/guide_fill_histogram.cpp) compiled_test(examples/guide_histogram_operators.cpp) @@ -139,9 +131,6 @@ if (TEST_SERIALIZATION) endif() if (BUILD_BENCHMARKS) - if (NOT ${CMAKE_BUILD_TYPE} EQUAL Release) - message(WARNING "Benchmarks should be build in Release mode") - endif() add_executable(speed_cpp test/speed_cpp.cpp) target_include_directories(speed_cpp PRIVATE include ${Boost_INCLUDE_DIR}) target_compile_definitions(speed_cpp PRIVATE -DBOOST_DISABLE_ASSERTS) diff --git a/Jamfile b/Jamfile index a04af372..46af9f49 100644 --- a/Jamfile +++ b/Jamfile @@ -8,9 +8,9 @@ project histogram : requirements - gcc:"-std=c++11 -pedantic -Wall -Wextra" - clang:"-std=c++11 -pedantic -Wall -Wextra" - darwin:"-std=c++11 -pedantic -Wall -Wextra" + gcc:"-std=c++14 -Wall -Wextra -Wno-noexcept-type" + clang:"-std=c++14 -Wall -Wextra" + darwin:"-std=c++14 -Wall -Wextra" ; build-project test ; diff --git a/doc/guide.qbk b/doc/guide.qbk index 951ba93b..0a9a924e 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -174,8 +174,15 @@ In C++, users can implement their own axis class without touching any library co The simplest way to make a custom axis is to derive from a builtin class. Here is a contrived example of a custom axis that inherits from the [classref boost::histogram::axis::integer integer axis] and accepts c-strings representing numbers. -[import ../examples/guide_custom_axis.cpp] -[guide_custom_axis] +[import ../examples/guide_custom_modified_axis.cpp] +[guide_custom_modified_axis] + +Alternatively, you can also make an axis completely from scratch. An minimal axis is a functor that maps an input to a bin index. The index has a range `[0, AxisType::size())`. + +[import ../examples/guide_custom_minimal_axis.cpp] +[guide_custom_minimal_axis] + +Such a minimal axis works, even though it lacks convenience features provided by the builtin axis types. For example, one cannot iterate over this axis. Not even a bin description can be queried, because `operator[]` is not implemented. It is up to the user to implement these optional aspects. [endsect] diff --git a/examples/Jamfile b/examples/Jamfile index e2e7708c..bf516331 100644 --- a/examples/Jamfile +++ b/examples/Jamfile @@ -4,6 +4,7 @@ # Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) +import testing ; project histogram-example : requirements @@ -11,17 +12,18 @@ project histogram-example . ; -exe getting_started_listing_01 : getting_started_listing_01.cpp ; -exe getting_started_listing_02 : getting_started_listing_02.cpp ; -exe guide_access_bin_counts : guide_access_bin_counts.cpp ; -exe guide_axis_with_labels : guide_axis_with_labels.cpp ; -exe guide_axis_with_uoflow_off : guide_axis_with_uoflow_off.cpp ; -exe guide_custom_axis : guide_custom_axis.cpp ; -exe guide_custom_storage : guide_custom_storage.cpp ; -exe guide_fill_histogram : guide_fill_histogram.cpp ; -exe guide_histogram_operators : guide_histogram_operators.cpp ; -exe guide_histogram_reduction : guide_histogram_reduction.cpp ; -exe guide_histogram_serialization : guide_histogram_serialization.cpp /boost/serialization//boost_serialization/static ; -exe guide_histogram_streaming : guide_histogram_streaming.cpp ; -exe guide_make_dynamic_histogram : guide_make_dynamic_histogram.cpp ; -exe guide_make_static_histogram : guide_make_static_histogram.cpp ; +run getting_started_listing_01.cpp ; +run getting_started_listing_02.cpp ; +run guide_access_bin_counts.cpp ; +run guide_axis_with_labels.cpp ; +run guide_axis_with_uoflow_off.cpp ; +run guide_custom_minimal_axis.cpp ; +run guide_custom_modified_axis.cpp ; +run guide_custom_storage.cpp ; +run guide_fill_histogram.cpp ; +run guide_histogram_operators.cpp ; +run guide_histogram_reduction.cpp ; +run guide_histogram_serialization.cpp /boost/serialization//boost_serialization/static ; +run guide_histogram_streaming.cpp ; +run guide_make_dynamic_histogram.cpp ; +run guide_make_static_histogram.cpp ; diff --git a/examples/getting_started_listing_01.cpp b/examples/getting_started_listing_01.cpp index fcdc6e4d..97c356ac 100644 --- a/examples/getting_started_listing_01.cpp +++ b/examples/getting_started_listing_01.cpp @@ -1,7 +1,10 @@ //[ getting_started_listing_01 #include -#include +#include +#include +#include +#include int main() { namespace bh = boost::histogram; @@ -11,12 +14,13 @@ int main() { create a static 1d-histogram with an axis that has 6 equidistant bins on the real line from -1.0 to 2.0, and label it as "x" */ - auto h = bh::make_static_histogram(bh::axis::regular<>(6, -1.0, 2.0, "x")); + auto h = bh::make_histogram(bh::axis::regular<>(6, -1.0, 2.0, "x")); - // fill histogram with data, typically this happens in a loop - // STL algorithms are supported + // Fill histogram with data, typically this happens in a loop. + // STL algorithms are supported, but make sure to use std::ref in + // the call to std::for_each to avoid copying the argument. auto data = {-0.5, 1.1, 0.3, 1.7}; - h = std::for_each(data.begin(), data.end(), h); + std::for_each(data.begin(), data.end(), std::ref(h)); /* a regular axis is a sequence of semi-open bins; extra under- and @@ -39,40 +43,37 @@ int main() { iterate over bins with a fancy histogram iterator - order in which bins are iterated over is an implementation detail - iterator dereferences to histogram::element_type, which is defined by - its storage class; by default something with value() and - variance() methods; the first returns the - actual count, the second returns a variance estimate of the count - (see Rationale section for what this means) + its storage class; by default this is a double - idx(N) method returns the index of the N-th axis - bin(N_c) method returns current bin of N-th axis; the suffx _c turns the argument into a compile-time number, which is needed to return - different `bin_type`s for different axes + a different `bin_type`s for each axis - `bin_type` usually is a semi-open interval representing the bin, whose edges can be accessed with methods `lower()` and `upper()`, but the implementation depends on the axis, please look it up in the reference */ - std::cout.setf(std::ios_base::fixed); + std::ostringstream os; + os.setf(std::ios_base::fixed); for (auto it = h.begin(); it != h.end(); ++it) { const auto bin = it.bin(0_c); - std::cout << "bin " << it.idx(0) << " x in [" << std::setprecision(1) - << std::setw(4) << bin.lower() << ", " << std::setw(4) - << bin.upper() << "): " << std::setprecision(1) << it->value() - << " +/- " << std::setprecision(3) << std::sqrt(it->variance()) - << std::endl; + os << "bin " << std::setw(2) << it.idx(0) << " [" << std::setprecision(1) + << std::setw(4) << bin.lower() << ", " << std::setw(4) + << bin.upper() << "): " << *it << "\n"; } - /* program output: (note that under- and overflow bins appear at the end) + std::cout << os.str() << std::endl; - bin 0 x in [-1.0, -0.5): 1 +/- 1 - bin 1 x in [-0.5, 0.0): 0 +/- 0 - bin 2 x in [ 0.0, 0.5): 1 +/- 1 - bin 3 x in [ 0.5, 1.0): 0 +/- 0 - bin 4 x in [ 1.0, 1.5): 0 +/- 0 - bin 5 x in [ 1.5, 2.0): 0 +/- 0 - bin 6 x in [ 2.0, inf): 2 +/- 1.41421 - bin -1 x in [-inf, -1): 1 +/- 1 - - */ + assert(os.str() == + "bin 0 [-1.0, -0.5): 1.0\n" + "bin 1 [-0.5, -0.0): 1.0\n" + "bin 2 [-0.0, 0.5): 2.0\n" + "bin 3 [ 0.5, 1.0): 0.0\n" + "bin 4 [ 1.0, 1.5): 1.0\n" + "bin 5 [ 1.5, 2.0): 1.0\n" + "bin 6 [ 2.0, inf): 2.0\n" + "bin -1 [-inf, -1.0): 1.0\n" + ); + // note how under- and overflow bins appear at the end } //] diff --git a/examples/getting_started_listing_02.cpp b/examples/getting_started_listing_02.cpp index 856706fe..d4c61d4b 100644 --- a/examples/getting_started_listing_02.cpp +++ b/examples/getting_started_listing_02.cpp @@ -8,50 +8,47 @@ namespace bh = boost::histogram; int main() { /* - create a dynamic histogram with the factory `make_dynamic_histogram` + Create a dynamic histogram with the factory `make_dynamic_histogram`. - axis can be passed directly just like for `make_static_histogram` - in addition, the factory also accepts iterators over a sequence of - axis::any, the polymorphic type that can hold concrete axis types + axis::variant, the polymorphic type that can hold concrete axis types */ - std::vector< - bh::axis::any< - bh::axis::regular<>, - bh::axis::category - > - > axes; + std::vector, bh::axis::category > > + axes; axes.emplace_back(bh::axis::category({"red", "blue"})); - axes.emplace_back(bh::axis::regular<>(5, 0, 1, "x")); - axes.emplace_back(bh::axis::regular<>(5, 0, 1, "y")); - auto h = bh::make_dynamic_histogram(axes.begin(), axes.end()); + axes.emplace_back(bh::axis::regular<>(3, 0, 1, "x")); + axes.emplace_back(bh::axis::regular<>(3, 0, 1, "y")); + auto h = bh::make_histogram(axes.begin(), axes.end()); // fill histogram with data, usually this happens in a loop h("red", 0.1, 0.2); - h("blue", 0.3, 0.4); - h("red", 0.5, 0.6); - h("red", 0.7, 0.8); + h("blue", 0.7, 0.3); + h("red", 0.3, 0.7); + h("red", 0.7, 0.7); /* - print dynamic histogram by iterating over bins - - for most axis types, the for loop looks just like for a static - histogram, except that we can pass runtime numbers, too - - if the [bin type] of the axis is not convertible to a - double interval, one needs to cast axis::any before looping; - this is here the case for the category axis + Print dynamic histogram by iterating over bins. + If the [bin type] of the axis is not convertible to a + double interval, you need to cast axis::variant before looping; + this is here the case for the category axis. */ using cas = bh::axis::category; - for (auto cbin : static_cast(h.axis(0))) { + for (auto cbin : bh::axis::get(h.axis(0))) { for (auto ybin : h.axis(2)) { // rows for (auto xbin : h.axis(1)) { // columns - const auto v = h.at(cbin, xbin, ybin).value(); + const auto v = h.at(cbin, xbin, ybin); if (v) - std::printf("%4s [%3.1f, %3.1f) [%3.1f, %3.1f) %3.0f\n", - cbin.value().c_str(), - xbin.lower(), xbin.upper(), - ybin.lower(), ybin.upper(), - v); + std::printf("(%i, %i, %i) %4s [%3.1f, %3.1f) [%3.1f, %3.1f) %3.0f\n", + cbin.idx(), xbin.idx(), ybin.idx(), cbin.value().c_str(), + xbin.lower(), xbin.upper(), ybin.lower(), ybin.upper(), v); } } } + + assert(h.at(0, 0, 0) == 1); + assert(h.at(0, 0, 2) == 1); + assert(h.at(0, 2, 2) == 1); + assert(h.at(1, 2, 0) == 1); } //] diff --git a/examples/guide_access_bin_counts.cpp b/examples/guide_access_bin_counts.cpp index 7c448ee3..a3b46fce 100644 --- a/examples/guide_access_bin_counts.cpp +++ b/examples/guide_access_bin_counts.cpp @@ -1,15 +1,15 @@ //[ guide_access_bin_counts #include -#include #include +#include namespace bh = boost::histogram; int main() { // make histogram with 2 x 2 = 4 bins (not counting under-/overflow bins) - auto h = bh::make_static_histogram(bh::axis::regular<>(2, -1, 1), - bh::axis::regular<>(2, 2, 4)); + auto h = bh::make_histogram(bh::axis::regular<>(2, -1, 1), + bh::axis::regular<>(2, 2, 4)); h(bh::weight(1), -0.5, 2.5); // bin index 0, 0 h(bh::weight(2), -0.5, 3.5); // bin index 0, 1 @@ -17,46 +17,45 @@ int main() { h(bh::weight(4), 0.5, 3.5); // bin index 1, 1 // access count value, number of indices must match number of axes - std::cout << h.at(0, 0).value() << " " << h.at(0, 1).value() << " " - << h.at(1, 0).value() << " " << h.at(1, 1).value() << std::endl; - - // prints: 1 2 3 4 - - // access count variance, number of indices must match number of axes - std::cout << h.at(0, 0).variance() << " " << h.at(0, 1).variance() << " " - << h.at(1, 0).variance() << " " << h.at(1, 1).variance() - << std::endl; - // prints: 1 4 9 16 - - // this is more efficient when you want to query value and variance - auto c11 = h.at(1, 1); - std::cout << c11.value() << " " << c11.variance() << std::endl; - // prints: 4 16 + assert(h.at(0, 0) == 1); + assert(h.at(0, 1) == 2); + assert(h.at(1, 0) == 3); + assert(h.at(1, 1) == 4); // histogram also supports access via container; using a container of // wrong size trips an assertion in debug mode auto idx = {0, 1}; - std::cout << h.at(idx).value() << std::endl; - // prints: 2 + assert(h.at(idx) == 2); // histogram also provides bin iterators - auto sum = std::accumulate(h.begin(), h.end(), - typename decltype(h)::element_type(0)); - std::cout << sum.value() << " " << sum.variance() << std::endl; - // prints: 10 30 + auto sum = std::accumulate(h.begin(), h.end(), 0.0); + assert(sum == 10); // bin iterators are fancy iterators with extra methods + // (note: iteration order is an implementation detail, don't rely on it) + std::ostringstream os; for (auto it = h.begin(), end = h.end(); it != end; ++it) { - const auto x = *it; - std::cout << it.idx(0) << " " << it.idx(1) << ": " - << x.value() << " " << x.variance() << std::endl; + os << std::setw(2) << it.idx(0) << " " << std::setw(2) << it.idx(1) << ": " << *it; } - // prints: (iteration order is an implementation detail, don't rely on it) - // 0 0: 1 1 - // 1 0: 3 9 - // ... - // 2 -1: 0 0 - // -1 -1: 0 0 + + assert(os.str() == + " 0 0: 1" + " 1 0: 3" + " 2 0: 0" + "-1 0: 0" + " 0 1: 2" + " 1 1: 4" + " 2 1: 0" + "-1 1: 0" + " 0 2: 0" + " 1 2: 0" + " 2 2: 0" + "-1 2: 0" + " 0 -1: 0" + " 1 -1: 0" + " 2 -1: 0" + "-1 -1: 0" + ); } //] diff --git a/examples/guide_axis_with_labels.cpp b/examples/guide_axis_with_labels.cpp index 2fe0dabc..eed20968 100644 --- a/examples/guide_axis_with_labels.cpp +++ b/examples/guide_axis_with_labels.cpp @@ -6,9 +6,10 @@ namespace bh = boost::histogram; int main() { // create a 2d-histogram with an "age" and an "income" axis - auto h = bh::make_static_histogram( + auto h = bh::make_histogram( bh::axis::regular<>(20, 0, 100, "age in years"), - bh::axis::regular<>(20, 0, 100, "yearly income in $1000")); + bh::axis::regular<>(20, 0, 100, "yearly income in $1000") + ); // do something with h } diff --git a/examples/guide_axis_with_uoflow_off.cpp b/examples/guide_axis_with_uoflow_off.cpp index 69e5fc65..e593d07a 100644 --- a/examples/guide_axis_with_uoflow_off.cpp +++ b/examples/guide_axis_with_uoflow_off.cpp @@ -6,8 +6,9 @@ namespace bh = boost::histogram; int main() { // create a 1d-histogram for dice throws with integer values from 1 to 6 - auto h = bh::make_static_histogram( - bh::axis::integer<>(1, 7, "eyes", bh::axis::uoflow_type::off)); + auto h = bh::make_histogram( + bh::axis::integer<>(1, 7, "eyes", bh::axis::option_type::none) + ); // do something with h } diff --git a/examples/guide_custom_axis.cpp b/examples/guide_custom_axis.cpp deleted file mode 100644 index f037286e..00000000 --- a/examples/guide_custom_axis.cpp +++ /dev/null @@ -1,39 +0,0 @@ -//[ guide_custom_axis - -#include -#include - -namespace bh = boost::histogram; - -// custom axis, which adapts builtin integer axis -struct custom_axis : public bh::axis::integer<> { - using value_type = const char*; // type that is fed to the axis - - using integer::integer; // inherit ctors of base - - // the customization point - // - accept const char* and convert to int - // - then call index method of base class - int index(value_type s) const { return integer::index(std::atoi(s)); } -}; - -int main() { - auto h = bh::make_static_histogram(custom_axis(0, 3)); - h("-10"); - h("0"); - h("1"); - h("9"); - - for (auto xi : h.axis()) { - std::cout << "bin " << xi.idx() << " [" << xi.lower() << ", " - << xi.upper() << ") " << h.at(xi).value() << std::endl; - } - - /* prints: - bin 0 [0, 1) 1 - bin 1 [1, 2] 1 - bin 2 [2, 3] 0 - */ -} - -//] diff --git a/examples/guide_custom_minimal_axis.cpp b/examples/guide_custom_minimal_axis.cpp new file mode 100644 index 00000000..7847b5a8 --- /dev/null +++ b/examples/guide_custom_minimal_axis.cpp @@ -0,0 +1,23 @@ +//[ guide_custom_minimal_axis + +#include +#include + +namespace bh = boost::histogram; + +// stateless axis which returns 1 if the input is even and 0 otherwise +struct minimal_axis { + int operator()(int x) const { return x % 2; } + unsigned size() const { return 2; } +}; + +int main() { + auto h = bh::make_histogram(minimal_axis()); + + h(0); h(1); h(2); + + assert(h.at(0) == 2); // two even numbers + assert(h.at(1) == 1); // one uneven number +} + +//] diff --git a/examples/guide_custom_modified_axis.cpp b/examples/guide_custom_modified_axis.cpp new file mode 100644 index 00000000..d146e4e8 --- /dev/null +++ b/examples/guide_custom_modified_axis.cpp @@ -0,0 +1,43 @@ +//[ guide_custom_modified_axis + +#include +#include +#include + +namespace bh = boost::histogram; + +// custom axis, which adapts builtin integer axis +struct custom_axis : public bh::axis::integer<> { + using value_type = const char*; // type that is fed to the axis + + using integer::integer; // inherit ctors of base + + // the customization point + // - accept const char* and convert to int + // - then call index method of base class + int operator()(value_type s) const { return integer::operator()(std::atoi(s)); } +}; + +int main() { + auto h = bh::make_histogram(custom_axis(3, 6)); + h("-10"); + h("3"); + h("4"); + h("9"); + + std::ostringstream os; + for (auto xi : h.axis()) { + os << "bin " << xi.idx() + << " [" << xi.lower() << ", " << xi.upper() << ") " + << h.at(xi) << "\n"; + } + + std::cout << os.str() << std::endl; + + assert(os.str() == + "bin 0 [3, 4) 1\n" + "bin 1 [4, 5) 1\n" + "bin 2 [5, 6) 0\n"); +} + +//] diff --git a/examples/guide_custom_storage.cpp b/examples/guide_custom_storage.cpp index 2114b19d..4649f75e 100644 --- a/examples/guide_custom_storage.cpp +++ b/examples/guide_custom_storage.cpp @@ -7,8 +7,8 @@ namespace bh = boost::histogram; int main() { // create static histogram with array_storage, using int as counter type - auto h = bh::make_static_histogram_with(bh::array_storage(), - bh::axis::regular<>(10, 0, 1)); + auto h = bh::make_histogram_with(bh::array_storage(), + bh::axis::regular<>(10, 0, 1)); // do something with h } diff --git a/examples/guide_fill_histogram.cpp b/examples/guide_fill_histogram.cpp index 625c0914..0d325d9d 100644 --- a/examples/guide_fill_histogram.cpp +++ b/examples/guide_fill_histogram.cpp @@ -3,12 +3,15 @@ #include #include #include +#include +#include +#include namespace bh = boost::histogram; int main() { - auto h = bh::make_static_histogram(bh::axis::regular<>(8, 0, 4), - bh::axis::regular<>(10, 0, 5)); + auto h = bh::make_histogram(bh::axis::regular<>(8, 0, 4), + bh::axis::regular<>(10, 0, 5)); // fill histogram, number of arguments must be equal to number of axes h(0, 1.1); // increases bin counter by one @@ -24,14 +27,17 @@ int main() { // functional-style processing is also supported std::vector> input_data{ {0, 1.2}, {2, 3.4}, {4, 5.6}}; - // std::for_each takes the functor by value, thus it potentially makes - // expensive copies of the histogram, but modern compilers are usually smart - // enough to avoid the superfluous copies - auto h2 = - std::for_each(input_data.begin(), input_data.end(), - bh::make_static_histogram(bh::axis::regular<>(8, 0, 4), - bh::axis::regular<>(10, 0, 5))); + + // std::for_each takes the functor by value, we use a reference wrapper + // to avoid costly copies + auto h2 = bh::make_histogram(bh::axis::regular<>(8, 0, 4), + bh::axis::regular<>(10, 0, 5)); + std::for_each(input_data.begin(), input_data.end(), + std::ref(h2)); + // h2 is filled + const double sum = std::accumulate(h2.begin(), h2.end(), 0.0); + assert(sum == 3); } //] diff --git a/examples/guide_histogram_operators.cpp b/examples/guide_histogram_operators.cpp index 1d0acb92..fb7ff54c 100644 --- a/examples/guide_histogram_operators.cpp +++ b/examples/guide_histogram_operators.cpp @@ -1,14 +1,14 @@ //[ guide_histogram_operators #include -#include +#include namespace bh = boost::histogram; int main() { // make two histograms - auto h1 = bh::make_static_histogram(bh::axis::regular<>(2, -1, 1)); - auto h2 = bh::make_static_histogram(bh::axis::regular<>(2, -1, 1)); + auto h1 = bh::make_histogram(bh::axis::regular<>(2, -1, 1)); + auto h2 = bh::make_histogram(bh::axis::regular<>(2, -1, 1)); h1(-0.5); // counts are: 1 0 h2(0.5); // counts are: 0 1 @@ -22,8 +22,7 @@ int main() { // accept and return rvalue references where possible auto h4 = h1 + h2 + h3; // counts are: 2 2 - std::cout << h4.at(0).value() << " " << h4.at(1).value() << std::endl; - // prints: 2 2 + assert(h4.at(0) == 2 && h4.at(1) == 2); // multiply by number h4 *= 2; // counts are: 4 4 @@ -31,27 +30,23 @@ int main() { // divide by number auto h5 = h4 / 4; // counts are: 1 1 - std::cout << h5.at(0).value() << " " << h5.at(1).value() << std::endl; - // prints: 1 1 + assert(h5.at(0) == 1 && h5.at(1) == 1); + assert(h4 != h5 && h4 == 4 * h5); - // compare histograms - std::cout << (h4 == 4 * h5) << " " << (h4 != h5) << std::endl; - // prints: 1 1 - - // note: special effect of multiplication on counter variance - auto h = bh::make_static_histogram(bh::axis::regular<>(2, -1, 1)); + // note special effect of multiplication on weight_counter variance + auto h = bh::make_histogram_with(bh::array_storage>(), + bh::axis::regular<>(2, -1, 1)); h(-0.5); // counts are: 1 0 - std::cout << "value " << (2 * h).at(0).value() << " " - << (h + h).at(0).value() << "\n" - << "variance " << (2 * h).at(0).variance() << " " - << (h + h).at(0).variance() << std::endl; - // equality operator also checks variances, so the statement is false - std::cout << (h + h == 2 * h) << std::endl; - /* prints: - value 2 2 - variance 4 2 - 0 - */ + + auto h_sum = h + h; + auto h_mul = 2 * h; + + // equality operator checks variances, so following statement is false + assert(h_sum != h_mul); + + // variance is different when histograms are scaled + assert(h_sum.at(0).value() == 2 && h_mul.at(0).value() == 2); + assert(h_sum.at(0).variance() == 2 && h_mul.at(0).variance() == 4); } //] diff --git a/examples/guide_histogram_reduction.cpp b/examples/guide_histogram_reduction.cpp index 43670ebc..145eafa8 100644 --- a/examples/guide_histogram_reduction.cpp +++ b/examples/guide_histogram_reduction.cpp @@ -1,7 +1,8 @@ //[ guide_histogram_reduction #include -#include +#include +#include namespace bh = boost::histogram; @@ -18,8 +19,8 @@ int main() { using namespace bh::literals; // enables _c suffix // make a 2d histogram - auto h = bh::make_static_histogram(bh::axis::regular<>(3, -1, 1), - bh::axis::integer<>(0, 4)); + auto h = bh::make_histogram(bh::axis::regular<>(3, -1, 1), + bh::axis::integer<>(0, 4)); h(-0.9, 0); h(0.9, 3); @@ -32,26 +33,26 @@ int main() { reduce does not remove counts; returned histograms are summed over the removed axes, so h, hr0, and hr1 have same number of total counts */ - std::cout << sum(h).value() << " " << sum(hr0).value() << " " - << sum(hr1).value() << std::endl; - // prints: 3 3 3 + assert(sum(h) == 3 && sum(hr0) == 3 && sum(hr1) == 3); + std::ostringstream os1; for (auto yi : h.axis(1_c)) { - for (auto xi : h.axis(0_c)) { std::cout << h.at(xi, yi).value() << " "; } - std::cout << std::endl; + for (auto xi : h.axis(0_c)) { os1 << h.at(xi, yi) << " "; } + os1 << "\n"; } - // prints: 1 0 0 - // 0 0 0 - // 0 1 0 - // 0 0 1 + assert(os1.str() == + "1 0 0 \n" + "0 0 0 \n" + "0 1 0 \n" + "0 0 1 \n"); - for (auto xi : hr0.axis()) std::cout << hr0.at(xi).value() << " "; - std::cout << std::endl; - // prints: 1 1 1 + std::ostringstream os2; + for (auto xi : hr0.axis()) os2 << hr0.at(xi) << " "; + assert(os2.str() == "1 1 1 "); - for (auto yi : hr1.axis()) std::cout << hr1.at(yi).value() << " "; - std::cout << std::endl; - // prints: 1 0 1 1 + std::ostringstream os3; + for (auto yi : hr1.axis()) os3 << hr1.at(yi) << " "; + assert(os3.str() == "1 0 1 1 "); } //] diff --git a/examples/guide_histogram_serialization.cpp b/examples/guide_histogram_serialization.cpp index 7f6faffe..6271d5d0 100644 --- a/examples/guide_histogram_serialization.cpp +++ b/examples/guide_histogram_serialization.cpp @@ -5,15 +5,16 @@ #include #include // includes serialization code #include +#include namespace bh = boost::histogram; int main() { - auto a = bh::make_static_histogram(bh::axis::regular<>(3, -1, 1, "r"), - bh::axis::integer<>(0, 2, "i")); + auto a = bh::make_histogram(bh::axis::regular<>(3, -1, 1, "axis 0"), + bh::axis::integer<>(0, 2, "axis 1")); a(0.5, 1); - std::string buf; // holds persistent representation + std::string buf; // to hold persistent representation // store histogram { @@ -25,8 +26,7 @@ int main() { auto b = decltype(a)(); // create a default-constructed second histogram - std::cout << "before restore " << (a == b) << std::endl; - // prints: before restore 0 + assert(b != a); // b is empty, a is not // load histogram { @@ -35,8 +35,7 @@ int main() { ia >> b; } - std::cout << "after restore " << (a == b) << std::endl; - // prints: after restore 1 + assert(b == a); // now b is equal to a } //] diff --git a/examples/guide_histogram_streaming.cpp b/examples/guide_histogram_streaming.cpp index 1c465671..913d03c8 100644 --- a/examples/guide_histogram_streaming.cpp +++ b/examples/guide_histogram_streaming.cpp @@ -2,37 +2,38 @@ #include #include -#include +#include +#include namespace bh = boost::histogram; int main() { - namespace axis = boost::histogram::axis; + namespace axis = bh::axis; - enum { A, B, C }; + auto h = bh::make_histogram( + axis::regular<>(2, -1, 1), + axis::regular>(2, 1, 10, "axis 1"), + axis::circular(4, 0.1, 1.0), // axis without metadata + axis::variable<>({-1, 0, 1}, "axis 3", axis::option_type::none), + axis::category<>({2, 1, 3}, "axis 4"), + axis::integer<>(-1, 1, "axis 5") + ); - auto h = bh::make_static_histogram( - axis::regular<>(2, -1, 1, "regular1", axis::uoflow_type::off), - axis::regular(2, 1, 10, "regular2"), - axis::circular<>(4, 0.1, 1.0, "polar"), - axis::variable<>({-1, 0, 1}, "variable", axis::uoflow_type::off), - axis::category<>({A, B, C}, "category"), - axis::integer<>(-1, 1, "integer", axis::uoflow_type::off)); + std::ostringstream os; + os << h; - std::cout << h << std::endl; + std::cout << os.str() << std::endl; - /* prints: - - histogram( - regular(2, -1, 1, label='regular1', uoflow=False), - regular_log(2, 1, 10, label='regular2'), - circular(4, phase=0.1, perimeter=1, label='polar'), - variable(-1, 0, 1, label='variable', uoflow=False), - category(0, 1, 2, label='category'), - integer(-1, 1, label='integer', uoflow=False), - ) - - */ + assert(os.str() == + "histogram(\n" + " regular(2, -1, 1, options=underflow_and_overflow),\n" + " regular_log(2, 1, 10, metadata=\"axis 1\", options=underflow_and_overflow),\n" + " circular(4, 0.1, 1.1, options=overflow),\n" + " variable(-1, 0, 1, metadata=\"axis 3\", options=none),\n" + " category(2, 1, 3, metadata=\"axis 4\", options=overflow),\n" + " integer(-1, 1, metadata=\"axis 5\", options=underflow_and_overflow),\n" + ")" + ); } //] diff --git a/examples/guide_make_dynamic_histogram.cpp b/examples/guide_make_dynamic_histogram.cpp index 6e090e6c..508e006a 100644 --- a/examples/guide_make_dynamic_histogram.cpp +++ b/examples/guide_make_dynamic_histogram.cpp @@ -2,27 +2,25 @@ #include #include +#include namespace bh = boost::histogram; int main() { // create vector of axes, axis::any is a polymorphic axis type - auto v = std::vector(); + auto v = std::vector, bh::axis::integer<> + >>(); v.push_back(bh::axis::regular<>(100, -1, 1)); v.push_back(bh::axis::integer<>(1, 7)); - // create dynamic histogram (make_static_histogram cannot be used with iterators) - auto h = bh::make_dynamic_histogram(v.begin(), v.end()); + // create dynamic histogram from iterator range + auto h = bh::make_histogram(v.begin(), v.end()); + assert(h.rank() == 2); - // do something with h - - - - // make_dynamic_histogram copies axis objects; to instead move the whole axis - // vector into the histogram, create a histogram instance directly - auto h2 = bh::histogram(std::move(v)); - - // do something with h2 + // create dynamic histogram by moving the vector (this avoids copies) + auto h2 = bh::make_histogram(std::move(v)); + assert(h2.rank() == 2); } //] diff --git a/examples/guide_make_static_histogram.cpp b/examples/guide_make_static_histogram.cpp index 35ac0468..6a940f69 100644 --- a/examples/guide_make_static_histogram.cpp +++ b/examples/guide_make_static_histogram.cpp @@ -1,18 +1,17 @@ //[ guide_make_static_histogram #include +#include namespace bh = boost::histogram; int main() { /* - create a 1d-histogram in default configuration which - covers the real line from -1 to 1 in 100 bins, the same - call with `make_dynamic_histogram` would also work + create a static 1d-histogram in default configuration + which covers the real line from -1 to 1 in 100 bins */ - auto h = bh::make_static_histogram(bh::axis::regular<>(100, -1, 1)); - - // do something with h + auto h = bh::make_histogram(bh::axis::regular<>(100, -1, 1)); + assert(h.rank() == 1); } //] diff --git a/histogram.sublime-project b/histogram.sublime-project index ae199ae8..d35d0ac4 100644 --- a/histogram.sublime-project +++ b/histogram.sublime-project @@ -9,7 +9,21 @@ { "ClangFormat": { + "style": "File", "format_on_save": true + }, + + "trim_trailing_white_space_on_save": false, + "ensure_newline_at_eof_on_save": false, + }, + "build_systems": + [ + { + "name": "CMake", + "working_dir": "${project_path}/build", + "cmd": ["make && ctest"], + "shell": true, + "file_regex": "/([^/:]+):(\\d+):(\\d+): ", } - } + ] } diff --git a/include/boost/histogram.hpp b/include/boost/histogram.hpp index 23470091..43f0b9ff 100644 --- a/include/boost/histogram.hpp +++ b/include/boost/histogram.hpp @@ -7,8 +7,12 @@ #ifndef BOOST_HISTOGRAM_HPP #define BOOST_HISTOGRAM_HPP -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/include/boost/histogram/axis/any.hpp b/include/boost/histogram/axis/any.hpp deleted file mode 100644 index ac068f24..00000000 --- a/include/boost/histogram/axis/any.hpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_HISTOGRAM_AXIS_ANY_HPP -#define BOOST_HISTOGRAM_AXIS_ANY_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forward declaration for serialization -namespace boost { -namespace serialization { -class access; -} // namespace serialization -} // namespace boost - -namespace boost { -namespace histogram { -namespace axis { - -namespace detail { - -// this visitation may be collapsed by the compiler almost into a direct cast, -// works with gcc-8 -O2 -DNDEBUG on Compiler Explorer at least -template -struct static_cast_visitor : public boost::static_visitor { - template - T operator()(A&& a) const { - return static_cast(std::forward(a)); - } -}; - -struct index_visitor : public boost::static_visitor { - const double x; - explicit index_visitor(const double arg) : x(arg) {} - template - int operator()(const Axis& a) const { - return impl(std::is_convertible(), a); - } - template - int impl(std::true_type, const Axis& a) const { - return a.index(static_cast(x)); - } - template - int impl(std::false_type, const Axis&) const { - throw std::runtime_error(boost::histogram::detail::cat( - "cannot convert double to ", - boost::typeindex::type_id().pretty_name(), " for ", - boost::typeindex::type_id().pretty_name())); - } -}; - -struct lower_visitor : public boost::static_visitor { - int idx; - lower_visitor(int i) : idx(i) {} - template - double operator()(const Axis& a) const { - return impl( - std::integral_constant< - bool, (std::is_convertible::value && - std::is_same>::value)>(), - a); - } - template - double impl(std::true_type, const Axis& a) const { - return a.lower(idx); - } - template - double impl(std::false_type, const Axis&) const { - throw std::runtime_error(boost::histogram::detail::cat( - "cannot use ", boost::typeindex::type_id().pretty_name(), - " with generic boost::histogram::axis::any interface, use" - " a static_cast to access the underlying axis type")); - } -}; - -struct bicmp_visitor : public boost::static_visitor { - template - bool operator()(const T&, const U&) const { - return false; - } - - template - bool operator()(const T& a, const T& b) const { - return a == b; - } -}; - -template -struct assign_visitor : public boost::static_visitor { - T& t; - assign_visitor(T& tt) : t(tt) {} - template - void operator()(const U& u) const { - impl(mp11::mp_contains(), u); - } - - template - void impl(mp11::mp_true, const U& u) const { - t = u; - } - - template - void impl(mp11::mp_false, const U&) const { - throw std::invalid_argument(boost::histogram::detail::cat( - "argument ", boost::typeindex::type_id().pretty_name(), - " is not a bounded type of ", boost::typeindex::type_id().pretty_name())); - } -}; - -struct get_label_visitor : public boost::static_visitor { - template - boost::string_view operator()(const T& t) const { - return t.label(); - } -}; - -struct set_label_visitor : public boost::static_visitor { - boost::string_view view; - set_label_visitor(boost::string_view v) : view(v) {} - template - void operator()(T& t) const { - t.label(view); - } -}; - -template -struct get_allocator_visitor : public boost::static_visitor { - template - U operator()(const T& t) const { - return t.get_allocator(); - } -}; - -} // namespace detail - -/// Polymorphic axis type -template -class any : public boost::variant { - using base_type = boost::variant; - -public: - using types = mp11::mp_list; - using value_type = double; - using bin_type = interval_view; - using const_iterator = iterator_over; - using const_reverse_iterator = reverse_iterator_over; - -private: - template - using requires_bounded_type = - mp11::mp_if>, void>; - -public: - any() = default; - any(const any&) = default; - any& operator=(const any&) = default; - any(any&&) = default; - any& operator=(any&&) = default; - - template > - any(T&& t) : base_type(std::forward(t)) {} - - template > - any& operator=(T&& t) { - base_type::operator=(std::forward(t)); - return *this; - } - - template - any(const any& u) { - boost::apply_visitor(detail::assign_visitor(*this), u); - } - - template - any& operator=(const any& u) { - boost::apply_visitor(detail::assign_visitor(*this), u); - return *this; - } - - int size() const { return static_cast(*this).size(); } - - int shape() const { return static_cast(*this).shape(); } - - bool uoflow() const { return static_cast(*this).uoflow(); } - - // note: this only works for axes with compatible value type - int index(const value_type x) const { - return boost::apply_visitor(detail::index_visitor(x), *this); - } - - string_view label() const { - return boost::apply_visitor(detail::get_label_visitor(), *this); - } - - void label(const string_view x) { - boost::apply_visitor(detail::set_label_visitor(x), *this); - } - - // this only works for axes with compatible bin type - // and will throw a runtime_error otherwise - double lower(int idx) const { - return boost::apply_visitor(detail::lower_visitor(idx), *this); - } - - bin_type operator[](const int idx) const { return bin_type(idx, *this); } - - bool operator==(const any& rhs) const { - return base_type::operator==(static_cast(rhs)); - } - - template - bool operator==(const any& u) const { - return boost::apply_visitor(detail::bicmp_visitor(), *this, u); - } - - template > - bool operator==(const T& t) const { - // variant::operator==(T) is implemented only to fail, we cannot use it - auto tp = boost::get(this); - return tp && *tp == t; - } - - template - bool operator!=(const T& t) const { - return !operator==(t); - } - - explicit operator const base&() const { - return boost::apply_visitor(detail::static_cast_visitor(), *this); - } - - explicit operator base&() { - return boost::apply_visitor(detail::static_cast_visitor(), *this); - } - - template - explicit operator const T&() const { - return boost::strict_get(*this); - } - - template - explicit operator T&() { - return boost::strict_get(*this); - } - - const_iterator begin() const { return const_iterator(*this, 0); } - const_iterator end() const { return const_iterator(*this, size()); } - const_reverse_iterator rbegin() const { return const_reverse_iterator(*this, size()); } - const_reverse_iterator rend() const { return const_reverse_iterator(*this, 0); } - -private: - friend class boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -} // namespace axis -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/axis/base.hpp b/include/boost/histogram/axis/base.hpp index 43dd60ea..1e79ddae 100644 --- a/include/boost/histogram/axis/base.hpp +++ b/include/boost/histogram/axis/base.hpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 Hans Dembinski +// Copyright 2015-2018 Hans Dembinski // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -7,128 +7,75 @@ #ifndef BOOST_HISTOGRAM_AXIS_BASE_HPP #define BOOST_HISTOGRAM_AXIS_BASE_HPP -#include -#include -#include -#include +#include +#include +#include +#include #include - -// forward declaration for serialization -namespace boost { -namespace serialization { -class access; -} // namespace serialization -} // namespace boost +#include namespace boost { namespace histogram { namespace axis { -enum class uoflow_type { off = 0, oflow = 1, on = 2 }; - /// Base class for all axes +template class base { + using metadata_type = MetaData; + public: - /// Returns the number of bins without overflow/underflow. - int size() const noexcept { return size_; } - /// Returns the number of bins, including overflow/underflow if enabled. - int shape() const noexcept { return shape_; } - /// Returns number of extra bins to count under- or overflow. - int uoflow() const noexcept { return shape_ - size_; } + /// Returns the number of bins, without extra bins. + unsigned size() const noexcept { return size_meta_.first(); } + /// Returns the options. + option_type options() const noexcept { return opt_; } + /// Returns the metadata. + metadata_type& metadata() noexcept { return size_meta_.second(); } + /// Returns the metadata (const version). + const metadata_type& metadata() const noexcept { return size_meta_.second(); } + + friend void swap(base& a, base& b) noexcept // ADL works with friend functions + { + using std::swap; + swap(a.size_meta_, b.size_meta_); + swap(a.opt_, b.opt_); + } + + template + void serialize(Archive&, unsigned); protected: - base(unsigned size, axis::uoflow_type uo) - : size_(size), shape_(size + static_cast(uo)) { - if (size_ == 0) { throw std::invalid_argument("bins > 0 required"); } + base(unsigned n, metadata_type&& m, option_type opt) + : size_meta_(n, std::move(m)), opt_(opt) { + if (size() == 0) { throw std::invalid_argument("bins > 0 required"); } + const auto max_index = + static_cast(std::numeric_limits::max() - static_cast(opt_)); + if (size() > max_index) + throw std::invalid_argument(detail::cat("bins <= ", max_index, " required")); } - base() = default; + base() : size_meta_(0), opt_(option_type::none) {} base(const base&) = default; base& operator=(const base&) = default; - base(base&& rhs) : size_(rhs.size_), shape_(rhs.shape_) { - rhs.size_ = 0; - rhs.shape_ = 0; - } + base(base&& rhs) : size_meta_(std::move(rhs.size_meta_)), opt_(rhs.opt_) {} base& operator=(base&& rhs) { if (this != &rhs) { - size_ = rhs.size_; - shape_ = rhs.shape_; - rhs.size_ = 0; - rhs.shape_ = 0; + size_meta_ = std::move(rhs.size_meta_); + opt_ = rhs.opt_; } return *this; } bool operator==(const base& rhs) const noexcept { - return size_ == rhs.size_ && shape_ == rhs.shape_; + return size() == rhs.size() && opt_ == rhs.opt_ && + detail::static_if>( + [&rhs](const auto& m) { return m == rhs.metadata(); }, + [](const auto&) { return true; }, metadata()); } private: - int size_ = 0, shape_ = 0; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/// Base class with a label -template -class labeled_base : public base { -public: - using allocator_type = Allocator; - - allocator_type get_allocator() const { return label_.get_allocator(); } - - /// Returns the axis label, which is a name or description. - boost::string_view label() const noexcept { return label_; } - /// Change the label of an axis. - void label(boost::string_view label) { label_.assign(label.begin(), label.end()); } - - bool operator==(const labeled_base& rhs) const noexcept { - return base::operator==(rhs) && label_ == rhs.label_; - } - -protected: - labeled_base() = default; - labeled_base(const labeled_base&) = default; - labeled_base& operator=(const labeled_base&) = default; - labeled_base(labeled_base&& rhs) = default; - labeled_base& operator=(labeled_base&& rhs) = default; - - labeled_base(unsigned size, axis::uoflow_type uo, string_view label, - const allocator_type& a) - : base(size, uo), label_(label.begin(), label.end(), a) {} - -private: - boost::container::basic_string, allocator_type> label_; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/// Iterator mixin, uses CRTP to inject iterator logic into Derived. -template -class iterator_mixin { -public: - using const_iterator = iterator_over; - using const_reverse_iterator = reverse_iterator_over; - - const_iterator begin() const noexcept { - return const_iterator(*static_cast(this), 0); - } - const_iterator end() const noexcept { - return const_iterator(*static_cast(this), - static_cast(this)->size()); - } - const_reverse_iterator rbegin() const noexcept { - return const_reverse_iterator(*static_cast(this), - static_cast(this)->size()); - } - const_reverse_iterator rend() const noexcept { - return const_reverse_iterator(*static_cast(this), 0); - } -}; + detail::compressed_pair size_meta_; + option_type opt_; +}; // namespace axis } // namespace axis } // namespace histogram diff --git a/include/boost/histogram/axis/category.hpp b/include/boost/histogram/axis/category.hpp new file mode 100644 index 00000000..88c4915d --- /dev/null +++ b/include/boost/histogram/axis/category.hpp @@ -0,0 +1,155 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_CATEGORY_HPP +#define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace axis { +/** Axis which maps unique values to bins (one on one). + * + * The axis maps a set of values to bins, following the order of + * arguments in the constructor. There is an optional overflow bin + * for this axis, which counts values that are not part of the set. + * Binning is a O(n) operation for n values in the worst case and O(1) in + * the best case. The value types must be equal-comparable. + */ +template +class category : public base, + public iterator_mixin> { + using base_type = base; + using metadata_type = MetaData; + using value_type = T; + using allocator_type = Allocator; + +public: + /** Construct from iterator range of unique values. + * + * \param begin begin of category range of unique values. + * \param end end of category range of unique values. + * \param metadata description of the axis. + * \param options extra bin options. + * \param allocator allocator instance to use. + */ + template > + category(It begin, It end, metadata_type m = metadata_type(), + option_type o = option_type::overflow, allocator_type a = allocator_type()) + : base_type(std::distance(begin, end), std::move(m), o), x_(nullptr, std::move(a)) { + x_.first() = detail::create_buffer_from_iter(x_.second(), base_type::size(), begin); + } + + /** Construct axis from iterable sequence of unique values. + * + * \param seq sequence of unique values. + * \param metadata description of the axis. + * \param options extra bin options. + * \param allocator allocator instance to use. + */ + template > + category(const C& iterable, metadata_type m = metadata_type(), + option_type o = option_type::overflow, allocator_type a = allocator_type()) + : category(std::begin(iterable), std::end(iterable), std::move(m), o, + std::move(a)) {} + + /** Construct axis from an initializer list of unique values. + * + * \param seq sequence of unique values. + * \param metadata description of the axis. + * \param options extra bin options. + * \param allocator allocator instance to use. + */ + template + category(std::initializer_list l, metadata_type m = metadata_type(), + option_type o = option_type::overflow, allocator_type a = allocator_type()) + : category(l.begin(), l.end(), std::move(m), o, std::move(a)) {} + + category() : x_(nullptr) {} + + category(const category& o) : base_type(o), x_(o.x_) { + x_.first() = + detail::create_buffer_from_iter(x_.second(), base_type::size(), o.x_.first()); + } + + category& operator=(const category& o) { + if (this != &o) { + if (base_type::size() != o.size()) { + detail::destroy_buffer(x_.second(), x_.first(), base_type::size()); + base_type::operator=(o); + x_ = o.x_; + x_.first() = + detail::create_buffer_from_iter(x_.second(), base_type::size(), o.x_.first()); + } else { + base_type::operator=(o); + std::copy(o.x_.first(), o.x_.first() + base_type::size(), x_.first()); + } + } + return *this; + } + + category(category&& o) : category() { + using std::swap; + swap(static_cast(*this), static_cast(o)); + swap(x_, o.x_); + } + + category& operator=(category&& o) { + if (this != &o) { + using std::swap; + swap(static_cast(*this), static_cast(o)); + swap(x_, o.x_); + } + return *this; + } + + ~category() { detail::destroy_buffer(x_.second(), x_.first(), base_type::size()); } + + /// Returns the bin index for the passed argument. + int operator()(const value_type& x) const noexcept { + const auto begin = x_.first(); + const auto end = begin + base_type::size(); + return std::distance(begin, std::find(begin, end, x)); + } + + /// Returns the value for the bin index (performs a range check). + const value_type& value(unsigned idx) const { + if (idx >= base_type::size()) throw std::out_of_range("category index out of range"); + return x_.first()[idx]; + } + + auto operator[](int idx) const noexcept { return value_bin_view(idx, *this); } + + bool operator==(const category& o) const noexcept { + return base_type::operator==(o) && + std::equal(x_.first(), x_.first() + base_type::size(), o.x_.first()); + } + + bool operator!=(const category& o) const noexcept { return !operator==(o); } + + template + void serialize(Archive&, unsigned); + +private: + using pointer = typename std::allocator_traits::pointer; + detail::compressed_pair x_; +}; +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/circular.hpp b/include/boost/histogram/axis/circular.hpp new file mode 100644 index 00000000..d4cd96a8 --- /dev/null +++ b/include/boost/histogram/axis/circular.hpp @@ -0,0 +1,92 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_CIRCULAR_HPP +#define BOOST_HISTOGRAM_AXIS_CIRCULAR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace axis { + +// two_pi can be found in boost/math, but it is defined here to reduce deps +constexpr double two_pi = 6.283185307179586; + +/** Axis for real values on a circle. + * + * The axis is circular and wraps around reaching the perimeter value. + * It has no underflow bin and the overflow bin merely counts special + * values like NaN and infinity. Binning is a O(1) operation. + */ +template +class circular : public base, + public iterator_mixin> { + using base_type = base; + using value_type = RealType; + using metadata_type = MetaData; + +public: + /** Construct n bins with an optional offset. + * + * \param n number of bins. + * \param phase starting phase. + * \param perimeter range after which value wraps around. + * \param metadata description of the axis. + * \param options extra bin options. + */ + circular(unsigned n, RealType phase = 0, RealType perimeter = two_pi, + MetaData m = MetaData(), option_type o = option_type::overflow) + : base_type(n, std::move(m), + o == option_type::underflow_and_overflow ? option_type::overflow : o) + , phase_(phase) + , delta_(perimeter / n) { + if (!std::isfinite(phase) || !(perimeter > 0)) + throw std::invalid_argument("invalid phase or perimeter"); + } + + circular() = default; + + /// Returns the bin index for the passed argument. + int operator()(value_type x) const noexcept { + const auto z = std::floor((x - phase_) / delta_); + if (std::isfinite(z)) { + const auto i = static_cast(z) % base_type::size(); + return i + (i < 0) * base_type::size(); + } + return base_type::size(); + } + + /// Returns axis value for fractional index. + value_type value(value_type i) const noexcept { return phase_ + i * delta_; } + + auto operator[](int idx) const noexcept { return interval_bin_view(idx, *this); } + + bool operator==(const circular& o) const noexcept { + return base_type::operator==(o) && phase_ == o.phase_ && delta_ == o.delta_; + } + + bool operator!=(const circular<>& o) const noexcept { return !operator==(o); } + + template + void serialize(Archive&, unsigned); + +private: + value_type phase_ = 0.0, delta_ = 1.0; +}; +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/integer.hpp b/include/boost/histogram/axis/integer.hpp new file mode 100644 index 00000000..e36e396a --- /dev/null +++ b/include/boost/histogram/axis/integer.hpp @@ -0,0 +1,98 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_INTEGER_HPP +#define BOOST_HISTOGRAM_AXIS_INTEGER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace axis { +/** Axis for an interval of integer values with unit steps. + * + * Binning is a O(1) operation. This axis operates + * faster than a regular axis. + */ +template +class integer : public base, public iterator_mixin> { + using base_type = base; + using value_type = IntType; + using metadata_type = MetaData; + using bin_view = std::conditional_t::value, + value_bin_view, interval_bin_view>; + +public: + /** 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 metadata description of the axis. + * \param options extra bin options. + */ + integer(value_type start, value_type stop, metadata_type m = metadata_type(), + option_type o = option_type::underflow_and_overflow) + : base_type(stop - start, std::move(m), o), min_(start) { + if (start >= stop) { throw std::invalid_argument("start < stop required"); } + } + + integer() = default; + + /// Returns the bin index for the passed argument. + int operator()(value_type x) const noexcept { + x = std::floor(x - min_); + return x >= 0 ? (x > static_cast(base_type::size()) ? base_type::size() + : static_cast(x)) + : -1; + } + + /// Returns axis value for index. + value_type value(value_type i) const noexcept { + if (i < 0) { + return detail::static_if>( + [](auto) { return std::numeric_limits::min(); }, + [](auto) { return -std::numeric_limits::infinity(); }, + 0 + ); + } + if (i > static_cast(base_type::size())) { + return detail::static_if>( + [](auto) { return std::numeric_limits::max(); }, + [](auto) { return std::numeric_limits::infinity(); }, + 0 + ); + } + return min_ + i; + } + + decltype(auto) operator[](int idx) const noexcept { return bin_view(idx, *this); } + + bool operator==(const integer& o) const noexcept { + return base_type::operator==(o) && min_ == o.min_; + } + + bool operator!=(const integer& o) const noexcept { return !operator==(o); } + + template + void serialize(Archive&, unsigned); + +private: + value_type min_ = 0; +}; +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/interval_bin_view.hpp b/include/boost/histogram/axis/interval_bin_view.hpp new file mode 100644 index 00000000..f98bf026 --- /dev/null +++ b/include/boost/histogram/axis/interval_bin_view.hpp @@ -0,0 +1,42 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_INTERVAL_BIN_VIEW_HPP +#define BOOST_HISTOGRAM_AXIS_INTERVAL_BIN_VIEW_HPP + +namespace boost { +namespace histogram { +namespace axis { + +template +class interval_bin_view { +public: + interval_bin_view(int idx, const Axis& axis) : idx_(idx), axis_(axis) {} + + int idx() const noexcept { return idx_; } + + decltype(auto) lower() const noexcept { return axis_.value(idx_); } + decltype(auto) upper() const noexcept { return axis_.value(idx_ + 1); } + decltype(auto) center() const noexcept { return axis_.value(idx_ + 0.5); } + decltype(auto) width() const noexcept { return upper() - lower(); } + + bool operator==(const interval_bin_view& rhs) const noexcept { + return idx_ == rhs.idx_ && axis_ == rhs.axis_; + } + bool operator!=(const interval_bin_view& rhs) const noexcept { return !operator==(rhs); } + + explicit operator int() const noexcept { return idx_; } + +private: + const int idx_; + const Axis& axis_; +}; + +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/interval_view.hpp b/include/boost/histogram/axis/interval_view.hpp deleted file mode 100644 index 121794fc..00000000 --- a/include/boost/histogram/axis/interval_view.hpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP -#define BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP - -#include -#include - -namespace boost { -namespace histogram { -namespace axis { - -template -class interval_view { -public: - interval_view(int idx, const Axis& axis) : idx_(idx), axis_(axis) {} - - interval_view(const interval_view&) = default; - interval_view& operator=(const interval_view&) = default; - interval_view(interval_view&&) = default; - interval_view& operator=(interval_view&&) = default; - - int idx() const noexcept { return idx_; } - - auto lower() const noexcept -> decltype(std::declval().lower(0)) { - return axis_.lower(idx_); - } - auto upper() const noexcept -> decltype(std::declval().lower(0)) { - return axis_.lower(idx_ + 1); - } - typename Axis::value_type width() const noexcept { - return upper() - lower(); - } - - bool operator==(const interval_view& rhs) const noexcept { - return idx_ == rhs.idx_ && axis_ == rhs.axis_; - } - bool operator!=(const interval_view& rhs) const noexcept { - return !operator==(rhs); - } - - explicit operator int() const noexcept { return idx_; } - -private: - const int idx_; - const Axis& axis_; -}; - -} // namespace axis -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/axis/iterator.hpp b/include/boost/histogram/axis/iterator.hpp index 04c676d8..227114b0 100644 --- a/include/boost/histogram/axis/iterator.hpp +++ b/include/boost/histogram/axis/iterator.hpp @@ -8,6 +8,7 @@ #define BOOST_HISTOGRAM_AXIS_ITERATOR_HPP #include +#include namespace boost { namespace histogram { @@ -15,12 +16,11 @@ namespace axis { template class iterator_over - : public iterator_facade, typename Axis::bin_type, + : public iterator_facade, decltype(std::declval()[0]), random_access_traversal_tag, - typename Axis::bin_type> { + decltype(std::declval()[0]), int> { public: - explicit iterator_over(const Axis& axis, int idx) - : axis_(axis), idx_(idx) {} + explicit iterator_over(const Axis& axis, int idx) : axis_(axis), idx_(idx) {} iterator_over(const iterator_over&) = default; iterator_over& operator=(const iterator_over&) = default; @@ -29,46 +29,38 @@ protected: void increment() noexcept { ++idx_; } void decrement() noexcept { --idx_; } void advance(int n) noexcept { idx_ += n; } - int distance_to(const iterator_over& other) const noexcept { - return other.idx_ - idx_; - } + int distance_to(const iterator_over& other) const noexcept { return other.idx_ - idx_; } bool equal(const iterator_over& other) const noexcept { return &axis_ == &other.axis_ && idx_ == other.idx_; } - typename Axis::bin_type dereference() const { return axis_[idx_]; } + decltype(std::declval()[0]) dereference() const { return axis_[idx_]; } + friend class ::boost::iterator_core_access; const Axis& axis_; int idx_; }; -template -class reverse_iterator_over - : public iterator_facade< - reverse_iterator_over, typename Axis::bin_type, - random_access_traversal_tag, typename Axis::bin_type> { +/// Uses CRTP to inject iterator logic into Derived. +template +class iterator_mixin { public: - explicit reverse_iterator_over(const Axis& axis, int idx) - : axis_(axis), idx_(idx) {} + using const_iterator = iterator_over; + using const_reverse_iterator = boost::reverse_iterator; - reverse_iterator_over(const reverse_iterator_over&) = default; - reverse_iterator_over& operator=(const reverse_iterator_over&) = default; - -protected: - void increment() noexcept { --idx_; } - void decrement() noexcept { ++idx_; } - void advance(int n) noexcept { idx_ -= n; } - int distance_to(const reverse_iterator_over& other) const noexcept { - return other.idx_ - idx_; + const_iterator begin() const noexcept { + return const_iterator(*static_cast(this), 0); } - bool equal(const reverse_iterator_over& other) const noexcept { - return &axis_ == &other.axis_ && idx_ == other.idx_; + const_iterator end() const noexcept { + return const_iterator(*static_cast(this), + static_cast(this)->size()); + } + const_reverse_iterator rbegin() const noexcept { + return boost::make_reverse_iterator(end()); + } + const_reverse_iterator rend() const noexcept { + return boost::make_reverse_iterator(begin()); } - typename Axis::bin_type dereference() const { return axis_[idx_ - 1]; } - friend class ::boost::iterator_core_access; - - const Axis& axis_; - int idx_; }; } // namespace axis diff --git a/include/boost/histogram/axis/ostream_operators.hpp b/include/boost/histogram/axis/ostream_operators.hpp index d5f6ba37..b3e19ec3 100644 --- a/include/boost/histogram/axis/ostream_operators.hpp +++ b/include/boost/histogram/axis/ostream_operators.hpp @@ -9,144 +9,183 @@ #ifndef BOOST_HISTOGRAM_AXIS_OSTREAM_OPERATORS_HPP #define BOOST_HISTOGRAM_AXIS_OSTREAM_OPERATORS_HPP -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include namespace boost { namespace histogram { -namespace axis { namespace detail { -inline string_view to_string(const transform::identity&) { return {}; } -inline string_view to_string(const transform::log&) { return {"_log", 4}; } -inline string_view to_string(const transform::sqrt&) { return {"_sqrt", 5}; } +template +const char* to_string(const axis::transform::identity&) { + return ""; +} +template +const char* to_string(const axis::transform::log&) { + return "_log"; +} +template +const char* to_string(const axis::transform::sqrt&) { + return "_sqrt"; +} +template +const char* to_string(const axis::transform::pow&) { + return "_pow"; +} + +template +void stream_metadata(OStream& os, const T& t) { + detail::static_if>( + [&os](const auto& t) { + std::ostringstream oss; + oss << t; + if (!oss.str().empty()) { os << ", metadata=" << std::quoted(oss.str()); } + }, + [&os](const auto&) { + using U = detail::rm_cvref; + os << ", metadata=" << boost::core::demangled_name(BOOST_CORE_TYPEID(U)); + }, + t); +} template -void escape_string(OStream& os, const string_view s) { - os << '\''; - for (auto sit = s.begin(); sit != s.end(); ++sit) { - if (*sit == '\'' && (sit == s.begin() || *(sit - 1) != '\\')) { - os << "\\\'"; - } else { - os << *sit; - } - } - os << '\''; +void stream_options(OStream& os, const axis::option_type o) { + os << ", options=" << o; } + +template +void stream_transform(OStream&, const T&) {} + +template +void stream_transform(OStream& os, const axis::transform::pow& t) { + os << ", power=" << t.power; +} + +template +void stream_value(OStream& os, const T& t) { + os << t; +} + +template +void stream_value(OStream& os, const std::basic_string& t) { + os << std::quoted(t); +} + } // namespace detail -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const interval_view& i) { +namespace axis { + +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const axis::option_type o) { + switch (o) { + case axis::option_type::none: + os << "none"; + break; + case axis::option_type::overflow: + os << "overflow"; + break; + case axis::option_type::underflow_and_overflow: + os << "underflow_and_overflow"; + break; + } + return os; +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const empty_metadata_type&) { + return os; // do nothing +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const interval_bin_view& i) { os << "[" << i.lower() << ", " << i.upper() << ")"; return os; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const value_view& i) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const value_bin_view& i) { os << i.value(); return os; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const regular& a) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const polymorphic_bin_view& i) { + if (i.is_continuous()) + os << "[" << i.lower() << ", " << i.upper() << ")"; + else + os << i.value(); + return os; +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const regular& a) { os << "regular" << detail::to_string(a.transform()) << "(" << a.size() << ", " - << a[0].lower() << ", " << a[a.size()].lower(); - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } - if (!a.uoflow()) { os << ", uoflow=False"; } + << a.value(0) << ", " << a.value(a.size()); + detail::stream_metadata(os, a.metadata()); + detail::stream_options(os, a.options()); + detail::stream_transform(os, a.transform()); os << ")"; return os; } -template -std::basic_ostream& operator<<( - std::basic_ostream& os, const regular& a) { - os << "regular_pow(" << a.size() << ", " << a[0].lower() << ", " << a[a.size()].lower() - << ", " << a.transform().power; - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } - if (!a.uoflow()) { os << ", uoflow=False"; } +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const circular& a) { + os << "circular(" << a.size() << ", " << a.value(0) << ", " << a.value(a.size()); + detail::stream_metadata(os, a.metadata()); + detail::stream_options(os, a.options()); os << ")"; return os; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const circular& a) { - os << "circular(" << a.size(); - if (a.phase() != 0.0) { os << ", phase=" << a.phase(); } - if (a.perimeter() != circular::two_pi()) { - os << ", perimeter=" << a.perimeter(); - } - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const variable& a) { + os << "variable(" << a.value(0); + for (unsigned i = 1; i <= a.size(); ++i) { os << ", " << a.value(i); } + detail::stream_metadata(os, a.metadata()); + detail::stream_options(os, a.options()); os << ")"; return os; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const variable& a) { - os << "variable(" << a[0].lower(); - for (int i = 1; i <= a.size(); ++i) { os << ", " << a[i].lower(); } - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } - if (!a.uoflow()) { os << ", uoflow=False"; } +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const integer& a) { + os << "integer(" << a.value(0) << ", " << a.value(a.size()); + detail::stream_metadata(os, a.metadata()); + detail::stream_options(os, a.options()); os << ")"; return os; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const integer& a) { - os << "integer(" << a[0].lower() << ", " << a[a.size()].lower(); - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } - if (!a.uoflow()) { os << ", uoflow=False"; } - os << ")"; - return os; -} - -template -std::basic_ostream& operator<<(std::basic_ostream& os, - const category& a) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const category& a) { os << "category("; - for (int i = 0; i < a.size(); ++i) { os << a[i] << (i == (a.size() - 1) ? "" : ", "); } - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } - os << ")"; - return os; -} - -template -inline std::basic_ostream& operator<<( - std::basic_ostream& os, const category& a) { - os << "category("; - for (int i = 0; i < a.size(); ++i) { - detail::escape_string(os, a.value(i)); + for (unsigned i = 0; i < a.size(); ++i) { + detail::stream_value(os, a.value(i)); os << (i == (a.size() - 1) ? "" : ", "); } - if (!a.label().empty()) { - os << ", label="; - detail::escape_string(os, a.label()); - } + detail::stream_metadata(os, a.metadata()); + detail::stream_options(os, a.options()); os << ")"; return os; } diff --git a/include/boost/histogram/axis/polymorphic_bin_view.hpp b/include/boost/histogram/axis/polymorphic_bin_view.hpp new file mode 100644 index 00000000..29830581 --- /dev/null +++ b/include/boost/histogram/axis/polymorphic_bin_view.hpp @@ -0,0 +1,67 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_VIEW_HPP +#define BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_VIEW_HPP + +#include + +namespace boost { +namespace histogram { +namespace axis { + +template +class polymorphic_bin_view { + using value_type = detail::return_type; +public: + polymorphic_bin_view(int idx, const Axis& axis, bool is_continuous) + : idx_(idx), axis_(axis), is_continuous_(is_continuous) {} + + int idx() const noexcept { return idx_; } + + value_type value() const { + if (is_continuous_) + throw std::runtime_error("calling value() for continuous axis is ambiguous"); + return axis_.value(idx_); + } + value_type lower() const { + if (!is_continuous_) + throw std::runtime_error("cannot call lower() for discontinuous axis"); + return axis_.value(idx_); + } + value_type upper() const { + if (!is_continuous_) + throw std::runtime_error("cannot call upper() for discontinuous axis"); + return axis_.value(idx_ + 1); + } + value_type center() const { + if (!is_continuous_) + throw std::runtime_error("cannot call center() for discontinuous axis"); + return axis_.value(idx_ + 0.5); + } + template () - std::declval())> + value_type width() const { return upper() - lower(); } + + bool operator==(const polymorphic_bin_view& rhs) const noexcept { + return idx_ == rhs.idx_ && axis_ == rhs.axis_; + } + bool operator!=(const polymorphic_bin_view& rhs) const noexcept { return !operator==(rhs); } + + explicit operator int() const noexcept { return idx_; } + + bool is_continuous() const noexcept { return is_continuous_; } + +private: + const int idx_; + const Axis& axis_; + const bool is_continuous_; +}; + +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/regular.hpp b/include/boost/histogram/axis/regular.hpp new file mode 100644 index 00000000..53c38f9e --- /dev/null +++ b/include/boost/histogram/axis/regular.hpp @@ -0,0 +1,179 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_REGULAR_HPP +#define BOOST_HISTOGRAM_AXIS_REGULAR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace axis { +namespace transform { +template +struct identity { + static T forward(T x) { return x; } + static T inverse(T x) { return x; } + + bool operator==(const identity&) const noexcept { return true; } + template + void serialize(Archive&, unsigned) {} // noop +}; + +template +struct log : identity { + static T forward(T x) { return std::log(x); } + static T inverse(T x) { return std::exp(x); } +}; + +template +struct sqrt : identity { + static T forward(T x) { return std::sqrt(x); } + static T inverse(T x) { return x * x; } +}; + +template +struct pow { + using U = mp11::mp_if, double, T>; + U power = 1.0; + + pow(U p) : power(p) {} + pow() = default; + + U forward(U v) const { return std::pow(v, power); } + U inverse(U v) const { return std::pow(v, 1.0 / power); } + + bool operator==(const pow& o) const noexcept { return power == o.power; } + template + void serialize(Archive&, unsigned); +}; + +template +struct unit { + using T = typename Q::value_type; + using U = typename Q::unit_type; + T forward(Q x) const { return x / U(); } + Q inverse(T x) const { return x * U(); } +}; +} // namespace transform + +/** Axis for equidistant intervals on the real line. + * + * The most common binning strategy. + * Very fast. Binning is a O(1) operation. + */ +template +class regular : public base, + public iterator_mixin>, + protected Transform { + using base_type = base; + using transform_type = Transform; + using external_type = detail::return_type; + using internal_type = detail::return_type; + static_assert(!std::is_integral::value, + "type returned by forward transform cannot be integral"); + using metadata_type = MetaData; + +public: + /** Construct n bins over real transformed range [begin, end). + * + * \param trans transform instance to use. + * \param n number of bins. + * \param start low edge of first bin. + * \param stop high edge of last bin. + * \param metadata description of the axis. + * \param options extra bin options. + */ + regular(transform_type trans, unsigned n, external_type start, external_type stop, + metadata_type m = {}, option_type o = option_type::underflow_and_overflow) + : base_type(n, std::move(m), o) + , transform_type(std::move(trans)) + , min_(this->forward(start)) + , delta_((this->forward(stop) - this->forward(start)) / n) { + if (!std::isfinite(min_) || !std::isfinite(delta_)) + throw std::invalid_argument("forward transform of start or stop invalid"); + if (delta_ == 0) + throw std::invalid_argument("range of forward transformed axis is zero"); + } + + /** Construct n bins over real range [begin, end). + * + * \param n number of bins. + * \param start low edge of first bin. + * \param stop high edge of last bin. + * \param metadata description of the axis. + * \param options extra bin options. + */ + regular(unsigned n, external_type start, external_type stop, metadata_type m = {}, + option_type o = option_type::underflow_and_overflow) + : regular({}, n, start, stop, std::move(m), o) {} + + regular() = default; + + /// Returns instance of the transform type + const transform_type& transform() const noexcept { return *this; } + + /// Returns the bin index for the passed argument. + int operator()(external_type x) const noexcept { + // Runs in hot loop, please measure impact of changes + const auto z = (this->forward(x) - min_) / delta_; + if (z < base_type::size()) { + if (z >= 0) + return static_cast(z); + else + return -1; + } + return base_type::size(); // also returned if z is NaN + + // const auto lt_max = z < base_type::size(); + // const auto ge_zero = z >= 0; + // return lt_max * (ge_zero * static_cast(z) - !ge_zero) + !lt_max * + // base_type::size(); + } + + /// Returns axis value for fractional index. + external_type value(internal_type i) const noexcept { + i /= base_type::size(); + if (i < 0) + i = std::copysign(std::numeric_limits::infinity(), -delta_); + else if (i > 1) + i = std::copysign(std::numeric_limits::infinity(), delta_); + else { + i = (1 - i) * min_ + i * (min_ + delta_ * base_type::size()); + } + return this->inverse(i); + } + + /// Access bin at index + auto operator[](int idx) const noexcept { return interval_bin_view(idx, *this); } + + bool operator==(const regular& o) const noexcept { + return base_type::operator==(o) && transform_type::operator==(o) && min_ == o.min_ && + delta_ == o.delta_; + } + + bool operator!=(const regular& o) const noexcept { return !operator==(o); } + + template + void serialize(Archive&, unsigned); + +private: + internal_type min_, delta_; +}; +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/traits.hpp b/include/boost/histogram/axis/traits.hpp new file mode 100644 index 00000000..82752332 --- /dev/null +++ b/include/boost/histogram/axis/traits.hpp @@ -0,0 +1,47 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP +#define BOOST_HISTOGRAM_AXIS_TRAITS_HPP + +#include +#include + +namespace boost { +namespace histogram { +namespace axis { +namespace traits { + template + using args = detail::args_type; + + template + decltype(auto) metadata(T&& t) noexcept { + return detail::static_if>( + [](auto&& x) -> decltype(auto) { return x.metadata(); }, + [](auto&&) -> detail::copy_qualifiers { + static axis::empty_metadata_type m; return m; + }, + t); + } + + template + option_type options(const T& t) noexcept { + return detail::static_if>( + [](const auto& x) { return x.options(); }, + [](const T&) { return axis::option_type::none; }, + t); + } + + template + unsigned extend(const T& t) noexcept { + return t.size() + static_cast(options(t)); + } +} // namespace traits +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/types.hpp b/include/boost/histogram/axis/types.hpp deleted file mode 100644 index d0b1ec39..00000000 --- a/include/boost/histogram/axis/types.hpp +++ /dev/null @@ -1,578 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_HISTOGRAM_AXIS_TYPES_HPP -#define BOOST_HISTOGRAM_AXIS_TYPES_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forward declaration for serialization -namespace boost { -namespace serialization { -class access; -} // namespace serialization -} // namespace boost - -namespace boost { -namespace histogram { -namespace axis { - -namespace transform { -namespace detail { -struct stateless { - bool operator==(const stateless&) const noexcept { return true; } - template - void serialize(Archive&, unsigned) {} -}; -} // namespace detail - -struct identity : public detail::stateless { - template - static T&& forward(T&& v) { - return std::forward(v); - } - template - static T&& inverse(T&& v) { - return std::forward(v); - } -}; - -struct log : public detail::stateless { - template - static T forward(T v) { - return std::log(v); - } - template - static T inverse(T v) { - return std::exp(v); - } -}; - -struct sqrt : public detail::stateless { - template - static T forward(T v) { - return std::sqrt(v); - } - template - static T inverse(T v) { - return v * v; - } -}; - -// struct cos : public detail::stateless { -// template static T forward(T v) { return std::cos(v); } -// template static T inverse(T v) { return std::acos(v); } -// }; - -struct pow { - double power = 1.0; - - pow() = default; - pow(double p) : power(p) {} - template - T forward(T v) const { - return std::pow(v, power); - } - template - T inverse(T v) const { - return std::pow(v, 1.0 / power); - } - bool operator==(const pow& other) const noexcept { return power == other.power; } - - template - void serialize(Archive&, unsigned); -}; -} // namespace transform - -/** Axis for equidistant intervals on the real line. - * - * The most common binning strategy. - * Very fast. Binning is a O(1) operation. - */ -// private inheritance from Transform wastes no space if it is stateless -template -class regular : public labeled_base, - public iterator_mixin>, - Transform { - using base_type = labeled_base; - -public: - using allocator_type = typename base_type::allocator_type; - using transform_type = Transform; - using value_type = RealType; - using bin_type = interval_view; - - static_assert(std::is_floating_point::value, - "type returned by forward transform must be floating point"); - - /** Construct axis with n bins over real range [lower, upper). - * - * \param n number of bins. - * \param lower low edge of first bin. - * \param upper high edge of last bin. - * \param label description of the axis. - * \param uoflow whether to add under-/overflow bins. - * \param trans arguments passed to the transform. - */ - regular(unsigned n, value_type lower, value_type upper, string_view label = {}, - uoflow_type uo = uoflow_type::on, transform_type trans = transform_type(), - const allocator_type& a = allocator_type()) - : base_type(n, uo, label, a) - , transform_type(std::move(trans)) - , min_(transform_type::forward(lower)) - , delta_((transform_type::forward(upper) - transform_type::forward(lower)) / n) { - if (lower < upper) { - BOOST_ASSERT(!std::isnan(min_)); - BOOST_ASSERT(!std::isnan(delta_)); - } else { - throw std::invalid_argument("lower < upper required"); - } - } - - regular() = default; - regular(const regular&) = default; - regular& operator=(const regular&) = default; - regular(regular&&) = default; - regular& operator=(regular&&) = default; - - /// Returns the bin index for the passed argument. - int index(value_type x) const noexcept { - // Optimized code, measure impact of changes - const value_type z = (transform_type::forward(x) - min_) / delta_; - return z < base_type::size() ? (z >= 0.0 ? static_cast(z) : -1) - : base_type::size(); - } - - /// Returns lower edge of bin. - value_type lower(int i) const noexcept { - const auto n = base_type::size(); - value_type x; - if (i < 0) - x = -std::numeric_limits::infinity(); - else if (i > n) - x = std::numeric_limits::infinity(); - else { - const auto z = value_type(i) / n; - x = (1.0 - z) * min_ + z * (min_ + delta_ * n); - } - return transform_type::inverse(x); - } - - bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } - - bool operator==(const regular& o) const noexcept { - return base_type::operator==(o) && transform_type::operator==(o) && min_ == o.min_ && - delta_ == o.delta_; - } - - /// Access properties of the transform. - const transform_type& transform() const noexcept { - return static_cast(*this); - } - -private: - value_type min_ = 0, delta_ = 1; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/** Axis for real values on a circle. - * - * The axis is circular and wraps around reaching the - * perimeter value. Therefore, there are no overflow/underflow - * bins for this axis. Binning is a O(1) operation. - */ -template -class circular : public labeled_base, - public iterator_mixin> { - using base_type = labeled_base; - -public: - using allocator_type = typename base_type::allocator_type; - using value_type = RealType; - using bin_type = interval_view; - - // two_pi can be found in boost/math, but it is defined here to reduce deps - static value_type two_pi() { return 6.283185307179586; } - - /** Constructor for n bins with an optional offset. - * - * \param n number of bins. - * \param phase starting phase. - * \param perimeter range after which value wraps around. - * \param label description of the axis. - */ - explicit circular(unsigned n, value_type phase = 0.0, value_type perimeter = two_pi(), - string_view label = {}, const allocator_type& a = allocator_type()) - : base_type(n, uoflow_type::off, label, a), phase_(phase), perimeter_(perimeter) { - if (perimeter <= 0) throw std::invalid_argument("perimeter must be positive"); - } - - circular() = default; - circular(const circular&) = default; - circular& operator=(const circular&) = default; - circular(circular&&) = default; - circular& operator=(circular&&) = default; - - /// Returns the bin index for the passed argument. - int index(value_type x) const noexcept { - const value_type z = (x - phase_) / perimeter_; - const int i = static_cast(std::floor(z * base_type::size())) % base_type::size(); - return i + (i < 0) * base_type::size(); - } - - /// Returns lower edge of bin. - value_type lower(int i) const noexcept { - const value_type z = value_type(i) / base_type::size(); - return z * perimeter_ + phase_; - } - - bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } - - bool operator==(const circular& o) const noexcept { - return base_type::operator==(o) && phase_ == o.phase_ && perimeter_ == o.perimeter_; - } - - value_type perimeter() const { return perimeter_; } - value_type phase() const { return phase_; } - -private: - value_type phase_ = 0.0, perimeter_ = 1.0; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/** Axis for non-equidistant bins on the real line. - * - * Binning is a O(log(N)) operation. If speed matters and the problem - * domain allows it, prefer a regular axis, possibly with a transform. - */ -template -class variable : public labeled_base, - public iterator_mixin> { - using base_type = labeled_base; - -public: - using allocator_type = typename base_type::allocator_type; - using value_type = RealType; - using bin_type = interval_view; - -private: - using value_allocator_type = - typename std::allocator_traits::template rebind_alloc; - using value_pointer_type = - typename std::allocator_traits::pointer; - -public: - /** Construct an axis from bin edges. - * - * \param x sequence of bin edges. - * \param label description of the axis. - * \param uoflow whether to add under-/overflow bins. - */ - variable(std::initializer_list x, string_view label = {}, - uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type()) - : variable(x.begin(), x.end(), label, uo, a) {} - - template > - variable(Iterator begin, Iterator end, string_view label = {}, - uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type()) - : base_type(begin == end ? 0 : std::distance(begin, end) - 1, uo, label, a) { - value_allocator_type a2(a); - using AT = std::allocator_traits; - x_ = AT::allocate(a2, nx()); - auto xit = x_; - try { - AT::construct(a2, xit, *begin++); - while (begin != end) { - if (*begin <= *xit) { - ++xit; // to make sure catch code works - throw std::invalid_argument("input sequence must be strictly ascending"); - } - ++xit; - AT::construct(a2, xit, *begin++); - } - } catch (...) { - // release resources that were already acquired before rethrowing - while (xit != x_) AT::destroy(a2, --xit); - AT::deallocate(a2, x_, nx()); - throw; - } - } - - variable() = default; - - variable(const variable& o) : base_type(o) { - value_allocator_type a(o.get_allocator()); - x_ = boost::histogram::detail::create_buffer_from_iter(a, nx(), o.x_); - } - - variable& operator=(const variable& o) { - if (this != &o) { - if (base_type::size() != o.size()) { - this->~variable(); - base::operator=(o); - value_allocator_type a(base_type::get_allocator()); - x_ = boost::histogram::detail::create_buffer_from_iter(a, nx(), o.x_); - } else { - base::operator=(o); - std::copy(o.x_, o.x_ + o.nx(), x_); - } - } - return *this; - } - - variable(variable&& o) : base_type(std::move(o)) { - x_ = o.x_; - o.x_ = nullptr; - } - - variable& operator=(variable&& o) { - this->~variable(); - base::operator=(std::move(o)); - x_ = o.x_; - o.x_ = nullptr; - return *this; - } - - ~variable() { - if (x_) { // nothing to do for empty state - value_allocator_type a(base_type::get_allocator()); - boost::histogram::detail::destroy_buffer(a, x_, nx()); - } - } - - /// Returns the bin index for the passed argument. - int index(value_type x) const noexcept { - return std::upper_bound(x_, x_ + nx(), x) - x_ - 1; - } - - /// Returns the starting edge of the bin. - value_type lower(int i) const noexcept { - if (i < 0) { return -std::numeric_limits::infinity(); } - if (i > base_type::size()) { return std::numeric_limits::infinity(); } - return x_[i]; - } - - bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } - - bool operator==(const variable& o) const noexcept { - if (!base::operator==(o)) { return false; } - return std::equal(x_, x_ + nx(), o.x_); - } - -private: - int nx() const { return base_type::size() + 1; } - - value_pointer_type x_ = nullptr; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/** Axis for an interval of integral values with unit steps. - * - * Binning is a O(1) operation. This axis operates - * faster than a regular. - */ -template -class integer : public labeled_base, - public iterator_mixin> { - using base_type = labeled_base; - -public: - using allocator_type = typename base_type::allocator_type; - using value_type = IntType; - using bin_type = interval_view; - - /** Construct axis over a semi-open integer interval [lower, upper). - * - * \param lower smallest integer of the covered range. - * \param upper largest integer of the covered range. - * \param label description of the axis. - * \param uoflow whether to add under-/overflow bins. - */ - integer(value_type lower, value_type upper, string_view label = {}, - uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type()) - : base_type(upper - lower, uo, label, a), min_(lower) { - if (!(lower < upper)) { throw std::invalid_argument("lower < upper required"); } - } - - integer() = default; - integer(const integer&) = default; - integer& operator=(const integer&) = default; - integer(integer&&) = default; - integer& operator=(integer&&) = default; - - /// Returns the bin index for the passed argument. - int index(value_type x) const noexcept { - const int z = x - min_; - return z >= 0 ? (z > base_type::size() ? base_type::size() : z) : -1; - } - - /// Returns lower edge of the integral bin. - value_type lower(int i) const noexcept { - if (i < 0) { return -std::numeric_limits::max(); } - if (i > base_type::size()) { return std::numeric_limits::max(); } - return min_ + i; - } - - bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } - - bool operator==(const integer& o) const noexcept { - return base_type::operator==(o) && min_ == o.min_; - } - -private: - value_type min_ = 0; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/** Axis which maps unique values to bins (one on one). - * - * The axis maps a set of values to bins, following the order of - * arguments in the constructor. There is an optional overflow bin - * for this axis, which counts values that are not part of the set. - * Binning is a O(n) operation for n values in the worst case and O(1) in - * the best case. The value types must be equal-comparable. - */ -template -class category : public labeled_base, - public iterator_mixin> { - using base_type = labeled_base; - -public: - using allocator_type = typename base_type::allocator_type; - using value_type = T; - using bin_type = value_view; - -private: - using value_allocator_type = - typename std::allocator_traits::template rebind_alloc; - using value_pointer_type = - typename std::allocator_traits::pointer; - -public: - /** Construct from an initializer list of strings. - * - * \param seq sequence of unique values. - * \param label description of the axis. - * \param uoflow whether to add under-/overflow bins. - */ - category(std::initializer_list seq, string_view label = {}, - uoflow_type uo = uoflow_type::oflow, - const allocator_type& a = allocator_type()) - : category(seq.begin(), seq.end(), label, uo, a) {} - - template > - category(Iterator begin, Iterator end, string_view label = {}, - uoflow_type uo = uoflow_type::oflow, - const allocator_type& a = allocator_type()) - : base_type(std::distance(begin, end), - uo == uoflow_type::on ? uoflow_type::oflow : uo, label, a) { - value_allocator_type a2(a); - x_ = boost::histogram::detail::create_buffer_from_iter(a2, nx(), begin); - } - - category() = default; - - category(const category& o) : base_type(o) { - value_allocator_type a(o.get_allocator()); - x_ = boost::histogram::detail::create_buffer_from_iter(a, o.nx(), o.x_); - } - - category& operator=(const category& o) { - if (this != &o) { - if (base_type::size() != o.size()) { - this->~category(); - base_type::operator=(o); - value_allocator_type a(base_type::get_allocator()); - x_ = boost::histogram::detail::create_buffer_from_iter(a, nx(), o.x_); - } else { - base_type::operator=(o); - std::copy(o.x_, o.x_ + o.nx(), x_); - } - } - return *this; - } - - category(category&& o) : base_type(std::move(o)) { - x_ = o.x_; - o.x_ = nullptr; - } - - category& operator=(category&& o) { - this->~category(); - base_type::operator=(std::move(o)); - x_ = o.x_; - o.x_ = nullptr; - return *this; - } - - ~category() { - if (x_) { // nothing to do for empty state - value_allocator_type a(base_type::get_allocator()); - boost::histogram::detail::destroy_buffer(a, x_, nx()); - } - } - - /// Returns the bin index for the passed argument. - int index(const value_type& x) const noexcept { - if (last_ < nx() && x_[last_] == x) return last_; - last_ = 0; - for (auto xit = x_, xe = x_ + nx(); xit != xe && !(*xit == x); ++xit) ++last_; - return last_; - } - - /// Returns the value for the bin index (performs a range check). - const value_type& value(int idx) const { - if (idx < 0 || idx >= base_type::size()) - throw std::out_of_range("category index out of range"); - return x_[idx]; - } - - bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } - - bool operator==(const category& o) const noexcept { - return base_type::operator==(o) && std::equal(x_, x_ + nx(), o.x_); - } - -private: - int nx() const { return base_type::size(); } - - value_pointer_type x_ = nullptr; - mutable int last_ = 0; - - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; -} // namespace axis -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/axis/value_bin_view.hpp b/include/boost/histogram/axis/value_bin_view.hpp new file mode 100644 index 00000000..ed3e8dff --- /dev/null +++ b/include/boost/histogram/axis/value_bin_view.hpp @@ -0,0 +1,41 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_VALUE_BIN_VIEW_HPP +#define BOOST_HISTOGRAM_AXIS_VALUE_BIN_VIEW_HPP + +#include + +namespace boost { +namespace histogram { +namespace axis { + +template +class value_bin_view { +public: + value_bin_view(int idx, const Axis& axis) : idx_(idx), axis_(axis) {} + + int idx() const noexcept { return idx_; } + + decltype(auto) value() const { return axis_.value(idx_); } + + bool operator==(const value_bin_view& rhs) const noexcept { + return idx_ == rhs.idx_ && axis_ == rhs.axis_; + } + bool operator!=(const value_bin_view& rhs) const noexcept { return !operator==(rhs); } + + explicit operator int() const noexcept { return idx_; } + +private: + const int idx_; + const Axis& axis_; +}; + +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/value_view.hpp b/include/boost/histogram/axis/value_view.hpp deleted file mode 100644 index 814b97fd..00000000 --- a/include/boost/histogram/axis/value_view.hpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_HISTOGRAM_AXIS_VALUE_VIEW_HPP -#define BOOST_HISTOGRAM_AXIS_VALUE_VIEW_HPP - -#include -#include - -namespace boost { -namespace histogram { -namespace axis { - -template -class value_view { -public: - value_view(int idx, const Axis& axis) : idx_(idx), axis_(axis) {} - - value_view(const value_view&) = default; - value_view& operator=(const value_view&) = default; - value_view(value_view&&) = default; - value_view& operator=(value_view&&) = default; - - int idx() const noexcept { return idx_; } - - auto value() const -> decltype(std::declval().value(0)) { - return axis_.value(idx_); - } - - bool operator==(const value_view& rhs) const noexcept { - return idx_ == rhs.idx_ && axis_ == rhs.axis_; - } - bool operator!=(const value_view& rhs) const noexcept { - return !operator==(rhs); - } - - explicit operator int() const noexcept { return idx_; } - -private: - const int idx_; - const Axis& axis_; -}; - -} // namespace axis -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/axis/variable.hpp b/include/boost/histogram/axis/variable.hpp new file mode 100644 index 00000000..787a65d4 --- /dev/null +++ b/include/boost/histogram/axis/variable.hpp @@ -0,0 +1,182 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_VARIABLE_HPP +#define BOOST_HISTOGRAM_AXIS_VARIABLE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace axis { +/** Axis for non-equidistant bins on the real line. + * + * Binning is a O(log(N)) operation. If speed matters and the problem + * domain allows it, prefer a regular axis, possibly with a transform. + */ +template +class variable : public base, + public iterator_mixin> { + using base_type = base; + using allocator_type = Allocator; + using metadata_type = MetaData; + using value_type = RealType; + +public: + /** Construct from iterator range of bin edges. + * + * \param begin begin of edge sequence. + * \param end end of edge sequence. + * \param metadata description of the axis. + * \param options extra bin options. + * \param allocator allocator instance to use. + */ + template > + variable(It begin, It end, metadata_type m = metadata_type(), + option_type o = option_type::underflow_and_overflow, + allocator_type a = allocator_type()) + : base_type(begin == end ? 0 : std::distance(begin, end) - 1, std::move(m), o) + , x_(nullptr, std::move(a)) { + using AT = std::allocator_traits; + x_.first() = AT::allocate(x_.second(), nx()); + try { + auto xit = x_.first(); + try { + AT::construct(x_.second(), xit, *begin++); + while (begin != end) { + if (*begin <= *xit) { + ++xit; // to make sure catch code works + throw std::invalid_argument("input sequence must be strictly ascending"); + } + ++xit; + AT::construct(x_.second(), xit, *begin++); + } + } catch (...) { + // release resources that were already acquired before rethrowing + while (xit != x_.first()) AT::destroy(x_.second(), --xit); + throw; + } + } catch (...) { + // release resources that were already acquired before rethrowing + AT::deallocate(x_.second(), x_.first(), nx()); + throw; + } + } + + /** Construct variable axis from iterable range of bin edges. + * + * \param iterable iterable range of bin edges. + * \param metadata description of the axis. + * \param options extra bin options. + * \param allocator allocator instance to use. + */ + template > + variable(const U& iterable, metadata_type m = metadata_type(), + option_type o = option_type::underflow_and_overflow, + allocator_type a = allocator_type()) + : variable(std::begin(iterable), std::end(iterable), std::move(m), o, + std::move(a)) {} + + /** Construct variable axis from initializer list of bin edges. + * + * \param edgelist list of of bin edges. + * \param metadata description of the axis. + * \param options extra bin options. + * \param allocator allocator instance to use. + */ + template + variable(const std::initializer_list& l, metadata_type m = metadata_type(), + option_type o = option_type::underflow_and_overflow, + allocator_type a = allocator_type()) + : variable(l.begin(), l.end(), std::move(m), o, std::move(a)) {} + + variable() : x_(nullptr) {} + + variable(const variable& o) : base_type(o), x_(o.x_) { + x_.first() = detail::create_buffer_from_iter(x_.second(), nx(), o.x_.first()); + } + + variable& operator=(const variable& o) { + if (this != &o) { + if (base_type::size() == o.size()) { + base_type::operator=(o); + std::copy(o.x_.first(), o.x_.first() + nx(), x_.first()); + } else { + detail::destroy_buffer(x_.second(), x_.first(), nx()); + base_type::operator=(o); + x_.second() = o.x_.second(); + x_.first() = detail::create_buffer_from_iter(x_.second(), nx(), o.x_.first()); + } + } + return *this; + } + + variable(variable&& o) : variable() { + using std::swap; + swap(static_cast(*this), static_cast(o)); + swap(x_, o.x_); + } + + variable& operator=(variable&& o) { + if (this != &o) { + using std::swap; + swap(static_cast(*this), static_cast(o)); + swap(x_, o.x_); + } + return *this; + } + + ~variable() { detail::destroy_buffer(x_.second(), x_.first(), nx()); } + + /// Returns the bin index for the passed argument. + int operator()(value_type x) const noexcept { + const auto p = x_.first(); + return std::upper_bound(p, p + nx(), x) - p - 1; + } + + /// Returns axis value for fractional index. + value_type value(value_type i) const noexcept { + if (i < 0) { return -std::numeric_limits::infinity(); } + if (i > base_type::size()) { return std::numeric_limits::infinity(); } + value_type z; + const int k = std::modf(i, &z); + return (1.0 - z) * x_.first()[k] + z * x_.first()[k + 1]; + } + + auto operator[](int idx) const noexcept { return interval_bin_view(idx, *this); } + + bool operator==(const variable& o) const noexcept { + return base_type::operator==(o) && + std::equal(x_.first(), x_.first() + nx(), o.x_.first()); + } + + bool operator!=(const variable<>& o) const noexcept { return !operator==(o); } + + template + void serialize(Archive&, unsigned); + +private: + int nx() const { return base_type::size() + 1; } + using pointer = typename std::allocator_traits::pointer; + detail::compressed_pair x_; +}; +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/axis/variant.hpp b/include/boost/histogram/axis/variant.hpp new file mode 100644 index 00000000..16687c6f --- /dev/null +++ b/include/boost/histogram/axis/variant.hpp @@ -0,0 +1,331 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_AXIS_VARIANT_HPP +#define BOOST_HISTOGRAM_AXIS_VARIANT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { + +namespace detail { +struct is_continuous : public boost::static_visitor { + template + bool operator()(const A&) const { + using T = detail::arg_type; + return !std::is_integral::value; + } +}; + +template +struct functor_wrapper : public boost::static_visitor { + F& fcn; + functor_wrapper(F& f) : fcn(f) {} + + template + R operator()(T&& t) const { + return fcn(std::forward(t)); + } +}; +} // namespace detail + +namespace axis { + +/// Polymorphic axis type +template +class variant : private boost::variant, public iterator_mixin> { + using base_type = boost::variant; + using first_bounded_type = mp11::mp_first; + using metadata_type = + detail::rm_cvref()))>; + + template + using requires_bounded_type = + mp11::mp_if>, void>; + +public: + variant() = default; + variant(const variant&) = default; + variant& operator=(const variant&) = default; + variant(variant&&) = default; + variant& operator=(variant&&) = default; + + template > + variant(T&& t) : base_type(std::forward(t)) {} + + template > + variant& operator=(T&& t) { + base_type::operator=(std::forward(t)); + return *this; + } + + template + variant(const variant& u) { + this->operator=(u); + } + + template + variant& operator=(const variant& u) { + visit( + [this](const auto& u) { + using U = detail::rm_cvref; + detail::static_if>( + [this](const auto& u) { this->operator=(u); }, + [](const auto&) { + throw std::runtime_error( + detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(U)), + " is not a bounded type of ", + boost::core::demangled_name(BOOST_CORE_TYPEID(variant)))); + }, + u); + }, + u); + return *this; + } + + unsigned size() const { + return visit([](const auto& x) { return x.size(); }, *this); + } + + option_type options() const { + return visit([](const auto& x) { return axis::traits::options(x); }, *this); + } + + const metadata_type& metadata() const { + return visit( + [](const auto& x) -> const metadata_type& { + using U = decltype(traits::metadata(x)); + return detail::static_if>( + [](const auto& x) -> const metadata_type& { return traits::metadata(x); }, + [](const auto&) -> const metadata_type& { + throw std::runtime_error(detail::cat( + "cannot return metadata of type ", + boost::core::demangled_name(BOOST_CORE_TYPEID(U)), + " through axis::variant interface which uses type ", + boost::core::demangled_name(BOOST_CORE_TYPEID(const metadata_type&)), + "; use boost::histogram::axis::get to obtain a reference " + "of this axis type")); + }, + x); + }, + *this); + } + + metadata_type& metadata() { + return visit( + [](auto& x) -> metadata_type& { + using U = decltype(traits::metadata(x)); + return detail::static_if>( + [](auto& x) -> metadata_type& { return traits::metadata(x); }, + [](auto&) -> metadata_type& { + throw std::runtime_error(detail::cat( + "cannot return metadata of type ", + boost::core::demangled_name(BOOST_CORE_TYPEID(U)), + " through axis::variant interface which uses type ", + boost::core::demangled_name(BOOST_CORE_TYPEID(metadata_type&)), + "; use boost::histogram::axis::get to obtain a reference " + "of this axis type")); + }, + x); + }, + *this); + } + + // Will throw invalid_argument exception if axis has incompatible call signature + template + int operator()(Us... x) const { + auto&& args = std::forward_as_tuple(std::forward(x)...); + return visit( + [&args](const auto& a) { + using A = detail::rm_cvref; + using args_t = std::tuple; + using expected_args_t = axis::traits::args; + return detail::static_if>( + [&args](const auto& a) -> int { return mp11::tuple_apply(a, args); }, + [](const auto&) -> int { + throw std::invalid_argument(detail::cat( + "cannot convert ", + boost::core::demangled_name(BOOST_CORE_TYPEID(args_t)), + " to ", + boost::core::demangled_name(BOOST_CORE_TYPEID(expected_args_t)), " for ", + boost::core::demangled_name(BOOST_CORE_TYPEID(A)), + "; use boost::histogram::axis::get to obtain a reference " + "of this axis type")); + }, + a); + }, + *this); + } + + // Only works for axes with value method that returns something convertible to + // double and will throw a runtime_error otherwise + double value(double idx) const { + return visit( + [idx](const auto& a) { + using T = detail::rm_cvref; + return detail::static_if>( + [idx](const auto& a) -> double { + using T = detail::rm_cvref; + using U = detail::return_type; + return detail::static_if>( + [idx](const auto& a) -> double { + return static_cast(a.value(idx)); + }, + [](const auto&) -> double { + throw std::runtime_error(detail::cat( + "return value ", + boost::core::demangled_name(BOOST_CORE_TYPEID(U)), " of ", + boost::core::demangled_name(BOOST_CORE_TYPEID(T)), + "::value(double) is not convertible to double; use " + "boost::histogram::axis::get to obtain a reference " + "of this axis type")); + }, + a); + }, + [](const auto&) -> double { + throw std::runtime_error( + detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(T)), + " has no value method; use " + "boost::histogram::axis::get to obtain a reference " + "of this axis type")); + }, + a); + }, + *this); + } + + decltype(auto) operator[](const int idx) const { + // using visit here causes internal error in MSVC 2017 + const bool is_continuous = boost::apply_visitor(detail::is_continuous(), + static_cast(*this)); + return polymorphic_bin_view(idx, *this, is_continuous); + } + + bool operator==(const variant& rhs) const { + return base_type::operator==(static_cast(rhs)); + } + + template + bool operator==(const variant& u) const { + return visit([&u](const auto& x) { return u == x; }, *this); + } + + template + bool operator==(const T& t) const { + // boost::variant::operator==(T) implemented only to fail, cannot use it + auto tp = boost::relaxed_get(this); + return tp && *tp == t; + } + + template + bool operator!=(const T& t) const { + return !operator==(t); + } + + template + void serialize(Archive& ar, unsigned); + + template + friend auto visit(Functor&& f, Variant&& v) + -> detail::visitor_return_type; + + template + friend T& get(variant& v); + + template + friend const T& get(const variant& v); + + template + friend T&& get(variant&& v); + + template + friend T* get(variant* v); + + template + friend const T* get(const variant* v); +}; // namespace histogram + +template +auto visit(Functor&& f, Variant&& v) -> detail::visitor_return_type { + using R = detail::visitor_return_type; + return boost::apply_visitor( + detail::functor_wrapper(f), + static_cast::base_type>>( + v)); +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const variant& v) { + visit( + [&os](const auto& x) { + using T = detail::rm_cvref; + detail::static_if>( + [&os](const auto& x) { os << x; }, + [](const auto&) { + throw std::runtime_error( + detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(T)), + " is not streamable")); + }, + x); + }, + v); + return os; +} + +template +T& get(variant& v) { + return boost::get(static_cast::base_type&>(v)); +} + +template +const T& get(const variant& v) { + return boost::get(static_cast::base_type&>(v)); +} + +template +T&& get(variant&& v) { + return boost::get(static_cast::base_type&&>(v)); +} + +template +T* get(variant* v) { + return boost::relaxed_get(static_cast::base_type*>(v)); +} + +template +const T* get(const variant* v) { + return boost::relaxed_get(static_cast::base_type*>(v)); +} + +// pass-through if T is an axis instead of a variant +template >, + typename = detail::requires_same>> +U get(U&& u) { + return std::forward(u); +} + +} // namespace axis +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/detail/axes.hpp b/include/boost/histogram/detail/axes.hpp index 0d94f7f1..215bc868 100644 --- a/include/boost/histogram/detail/axes.hpp +++ b/include/boost/histogram/detail/axes.hpp @@ -10,12 +10,11 @@ #include #include #include -#include +#include +#include #include #include #include -#include -#include #include #include #include @@ -37,7 +36,7 @@ struct axes_equal_static_dynamic_impl { template void operator()(N) const { using T = mp11::mp_at; - auto tp = boost::relaxed_get(&v[N::value]); + auto tp = axis::get(&v[N::value]); equal &= (tp && *tp == std::get(t)); } }; @@ -60,7 +59,7 @@ struct axes_assign_static_dynamic_impl { template void operator()(N) const { using T = mp11::mp_at; - std::get(t) = static_cast(v[N::value]); + std::get(t) = axis::get(v[N::value]); } }; @@ -150,42 +149,19 @@ void range_check(const std::tuple&) { static_assert(N < sizeof...(Ts), "index out of range"); } -namespace { -template -struct axis_at_impl {}; +template +using axis_at = mp_at_c::value * N>; -template -struct axis_at_impl> { - using type = mp11::mp_at_c, N>; -}; - -template -struct axis_at_impl> { - using type = Any; -}; +template > +auto axis_get(T&& axes) -> decltype(std::get(std::forward(axes))) { + return std::get(std::forward(axes)); } -template -using axis_at = typename axis_at_impl::type; - -template -auto axis_get(std::tuple& axes) -> axis_at>& { - return std::get(axes); -} - -template -auto axis_get(const std::tuple& axes) -> const axis_at>& { - return std::get(axes); -} - -template -Any& axis_get(std::vector& axes) { - return axes[N]; -} - -template -const Any& axis_get(const std::vector& axes) { - return axes[N]; +template > +auto axis_get(T&& axes) -> decltype(std::forward(axes)[N]) { + return std::forward(axes)[N]; } template @@ -193,22 +169,10 @@ void for_each_axis(const std::tuple& axes, F&& f) { mp11::tuple_for_each(axes, std::forward(f)); } -namespace { -template -struct unary_adaptor : public boost::static_visitor { - Unary&& unary; - unary_adaptor(Unary&& u) : unary(std::forward(u)) {} - template - void operator()(const T& a) const { - unary(a); - } -}; -} - -template -void for_each_axis(const std::vector& axes, F&& f) { +template +void for_each_axis(const std::vector& axes, F&& f) { for (const auto& x : axes) { - boost::apply_visitor(unary_adaptor(std::forward(f)), x); + axis::visit(std::forward(f), x); } } @@ -217,7 +181,7 @@ struct field_counter { std::size_t value = 1; template void operator()(const T& t) { - value *= t.shape(); + value *= axis::traits::extend(t); } }; } @@ -256,8 +220,8 @@ struct shape_collector { std::vector::iterator iter; shape_collector(std::vector::iterator i) : iter(i) {} template - void operator()(const T& a) { - *iter++ = a.shape(); + void operator()(const T& t) { + *iter++ = axis::traits::extend(t); } }; @@ -359,8 +323,8 @@ template void indices_to_index(optional_index& idx, const Axes& axes, const int j, const Us... us) { const auto& a = axis_get(axes); - const auto a_size = a.size(); - const auto a_shape = a.shape(); + const auto a_size = static_cast(a.size()); + const auto a_shape = static_cast(axis::traits::extend(a)); idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid linearize(idx, a_size, a_shape, j); indices_to_index<(D + 1)>(idx, axes, us...); @@ -375,8 +339,8 @@ void indices_to_index_iter(mp11::mp_size_t, optional_index& idx, const std::tuple& axes, Iterator iter) { constexpr auto D = mp11::mp_size_t() - N; const auto& a = std::get(axes); - const auto a_size = a.size(); - const auto a_shape = a.shape(); + const auto a_size = static_cast(a.size()); + const auto a_shape = axis::traits::extend(a); const auto j = static_cast(*iter); idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid linearize(idx, a_size, a_shape, j); @@ -387,8 +351,8 @@ template void indices_to_index_iter(optional_index& idx, const std::vector& axes, Iterator iter) { for (const auto& a : axes) { - const auto a_size = a.size(); - const auto a_shape = a.shape(); + const auto a_size = static_cast(a.size()); + const auto a_shape = axis::traits::extend(a); const auto j = static_cast(*iter++); idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid linearize(idx, a_size, a_shape, j); @@ -403,8 +367,8 @@ void indices_to_index_get(mp11::mp_size_t, optional_index& idx, const Axes& a const T& t) { constexpr std::size_t D = mp_size() - N; const auto& a = axis_get(axes); - const auto a_size = a.size(); - const auto a_shape = a.shape(); + const auto a_size = static_cast(a.size()); + const auto a_shape = axis::traits::extend(a); const auto j = static_cast(std::get(t)); idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid linearize(idx, a_size, a_shape, j); @@ -417,9 +381,10 @@ void args_to_index(optional_index&, const std::tuple&) noexcept {} template void args_to_index(optional_index& idx, const std::tuple& axes, const U& u, const Us&... us) { - const auto a_size = std::get(axes).size(); - const auto a_shape = std::get(axes).shape(); - const auto j = std::get(axes).index(u); + const auto& a = std::get(axes); + const auto a_size = a.size(); + const auto a_shape = axis::traits::extend(a); + const auto j = a(u); linearize(idx, a_size, a_shape, j); args_to_index<(D + 1)>(idx, axes, us...); } @@ -434,8 +399,8 @@ void args_to_index_iter(mp11::mp_size_t, optional_index& idx, constexpr std::size_t D = sizeof...(Ts)-N; const auto& a = axis_get(axes); const auto a_size = a.size(); - const auto a_shape = a.shape(); - const auto j = a.index(*iter); + const auto a_shape = axis::traits::extend(a); + const auto j = a(*iter); linearize(idx, a_size, a_shape, j); args_to_index_iter(mp11::mp_size_t<(N - 1)>(), idx, axes, ++iter); } @@ -448,38 +413,43 @@ template void args_to_index_get(mp11::mp_size_t, optional_index& idx, const std::tuple& axes, const T& t) { constexpr std::size_t D = mp_size::value - N; - const auto a_size = std::get(axes).size(); - const auto a_shape = std::get(axes).shape(); - const auto j = std::get(axes).index(std::get(t)); + const auto& a = std::get(axes); + const auto a_size = a.size(); + const auto a_shape = axis::traits::extend(a); + const auto j = a(std::get(t)); linearize(idx, a_size, a_shape, j); args_to_index_get(mp11::mp_size_t<(N - 1)>(), idx, axes, t); } namespace { template -struct args_to_index_visitor : public boost::static_visitor { +struct args_to_index_visitor { optional_index& idx; const T& val; args_to_index_visitor(optional_index& i, const T& v) : idx(i), val(v) {} - template - void operator()(const Axis& a) const { - impl(std::is_convertible(), a); + template + void operator()(const U& a) const { + using arg_type = mp11::mp_first>; + impl(std::is_convertible(), a); } - template - void impl(std::true_type, const Axis& a) const { + template + void impl(std::true_type, const U& a) const { + using arg_type = mp11::mp_first>; const auto a_size = a.size(); - const auto a_shape = a.shape(); - const auto j = a.index(static_cast(val)); + const auto a_shape = axis::traits::extend(a); + const auto j = a(static_cast(val)); linearize(idx, a_size, a_shape, j); } - template - void impl(std::false_type, const Axis&) const { + template + void impl(std::false_type, const U&) const { + using arg_type = mp11::mp_first>; throw std::invalid_argument(detail::cat( - "axis ", boost::typeindex::type_id().pretty_name(), ": argument ", - boost::typeindex::type_id().pretty_name(), " not convertible to value_type ", - boost::typeindex::type_id().pretty_name())); + boost::core::demangled_name( BOOST_CORE_TYPEID(U) ), + ": cannot convert argument of type ", + boost::core::demangled_name( BOOST_CORE_TYPEID(T) ), " to ", + boost::core::demangled_name( BOOST_CORE_TYPEID(arg_type) ))); } }; } @@ -490,7 +460,7 @@ void args_to_index(optional_index&, const std::vector&) {} template void args_to_index(optional_index& idx, const std::vector& axes, const U& u, const Us&... us) { - boost::apply_visitor(args_to_index_visitor(idx, u), axes[D]); + axis::visit(args_to_index_visitor(idx, u), axes[D]); args_to_index<(D + 1)>(idx, axes, us...); } @@ -499,7 +469,7 @@ void args_to_index_iter(optional_index& idx, const std::vector& axes, Iterator iter) { for (const auto& a : axes) { // iter could be a plain pointer, so we cannot use nested value_type here - boost::apply_visitor(args_to_index_visitor(idx, *iter++), a); + axis::visit(args_to_index_visitor(idx, *iter++), a); } } @@ -512,7 +482,7 @@ void args_to_index_get(mp11::mp_size_t, optional_index& idx, const std::vector& axes, const T& t) { constexpr std::size_t D = mp_size::value - N; using U = decltype(std::get(t)); - boost::apply_visitor(args_to_index_visitor(idx, std::get(t)), axes[D]); + axis::visit(args_to_index_visitor(idx, std::get(t)), axes[D]); args_to_index_get(mp11::mp_size_t<(N - 1)>(), idx, axes, t); } @@ -544,7 +514,7 @@ optional_index call_impl(static_container_tag, const std::tuple& } template -optional_index call_impl(dynamic_container_tag, const std::tuple& axes, +optional_index call_impl(iterable_container_tag, const std::tuple& axes, const U& u) { dimension_check(axes, u.size()); optional_index i; @@ -573,7 +543,7 @@ optional_index call_impl(static_container_tag, const std::vector& axes, } template -optional_index call_impl(dynamic_container_tag, const std::vector& axes, +optional_index call_impl(iterable_container_tag, const std::vector& axes, const U& u) { if (axes.size() == 1) // do not unpack for 1d histograms, it is ambiguous return call_impl(no_container_tag(), axes, u); @@ -608,7 +578,7 @@ std::size_t at_impl(detail::static_container_tag, const A& axes, const U& u) { } template -std::size_t at_impl(detail::dynamic_container_tag, const std::tuple& axes, +std::size_t at_impl(detail::iterable_container_tag, const std::tuple& axes, const U& u) { dimension_check(axes, std::distance(std::begin(u), std::end(u))); auto index = detail::optional_index(); @@ -619,7 +589,7 @@ std::size_t at_impl(detail::dynamic_container_tag, const std::tuple& axes } template -std::size_t at_impl(detail::dynamic_container_tag, const std::vector& axes, +std::size_t at_impl(detail::iterable_container_tag, const std::vector& axes, const U& u) { dimension_check(axes, std::distance(std::begin(u), std::end(u))); auto index = detail::optional_index(); diff --git a/include/boost/histogram/detail/buffer.hpp b/include/boost/histogram/detail/buffer.hpp index a81f6c6e..6e49faf8 100644 --- a/include/boost/histogram/detail/buffer.hpp +++ b/include/boost/histogram/detail/buffer.hpp @@ -91,6 +91,8 @@ typename std::allocator_traits::pointer create_buffer_from_iter( template void destroy_buffer(Allocator& a, typename std::allocator_traits::pointer p, std::size_t n) { + if (!p) + return; using AT = std::allocator_traits; auto it = p + n; const auto end = p; diff --git a/include/boost/histogram/detail/compressed_pair.hpp b/include/boost/histogram/detail/compressed_pair.hpp new file mode 100644 index 00000000..70d32c4d --- /dev/null +++ b/include/boost/histogram/detail/compressed_pair.hpp @@ -0,0 +1,74 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_DETAIL_COMPRESSED_PAIR_HPP +#define BOOST_HISTOGRAM_DETAIL_COMPRESSED_PAIR_HPP + +#include +#include + +namespace boost { +namespace histogram { +namespace detail { + +template +class compressed_pair_impl; + +template +class compressed_pair_impl : protected T2 { +public: + template + compressed_pair_impl(U1&& u1, U2&& u2) + : T2(std::forward(u2)), first_(std::forward(u1)) {} + template + compressed_pair_impl(U1&& u1) : first_(std::forward(u1)) {} + compressed_pair_impl() = default; + + T1& first() { return first_; } + T2& second() { return static_cast(*this); } + const T1& first() const { return first_; } + const T2& second() const { return static_cast(*this); } + +private: + T1 first_; +}; + +template +class compressed_pair_impl { +public: + template + compressed_pair_impl(U1&& u1, U2&& u2) + : first_(std::forward(u1)), second_(std::forward(u2)) {} + template + compressed_pair_impl(U1&& u1) : first_(std::forward(u1)) {} + compressed_pair_impl() = default; + + T1& first() { return first_; } + T2& second() { return second_; } + const T1& first() const { return first_; } + const T2& second() const { return second_; } + +private: + T1 first_; + T2 second_; +}; + +template +void swap(compressed_pair_impl& a, compressed_pair_impl& b) { + using std::swap; + swap(a.first(), b.first()); + swap(a.second(), b.second()); +} + +template +using compressed_pair = + compressed_pair_impl::value && std::is_empty::value)>; + +} // namespace detail +} // namespace histogram +} // namespace boost + +#endif \ No newline at end of file diff --git a/include/boost/histogram/detail/index_cache.hpp b/include/boost/histogram/detail/index_cache.hpp index af3cd889..25884ace 100644 --- a/include/boost/histogram/detail/index_cache.hpp +++ b/include/boost/histogram/detail/index_cache.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace boost { namespace histogram { @@ -35,19 +36,19 @@ struct index_cache : public std::unique_ptr { struct dim_visitor { mutable std::size_t stride; mutable block_t* b; - template - void operator()(const Axis& a) const noexcept { - b->dim = dim_t{0, a.size(), stride}; + template + void operator()(const T& a) const noexcept { + b->dim = dim_t{0, static_cast(a.size()), stride}; ++b; - stride *= a.shape(); + stride *= axis::traits::extend(a); } }; template void set(const H& h) { - if (!(*this) || h.dim() != ptr_t::get()->state.dim) { - ptr_t::reset(new block_t[h.dim() + 1]); - ptr_t::get()->state.dim = h.dim(); + if (!(*this) || h.rank() != ptr_t::get()->state.dim) { + ptr_t::reset(new block_t[h.rank() + 1]); + ptr_t::get()->state.dim = h.rank(); ptr_t::get()->state.idx = 0; } h.for_each_axis(dim_visitor{1, ptr_t::get() + 1}); diff --git a/include/boost/histogram/detail/meta.hpp b/include/boost/histogram/detail/meta.hpp index ef807717..efec949c 100644 --- a/include/boost/histogram/detail/meta.hpp +++ b/include/boost/histogram/detail/meta.hpp @@ -7,6 +7,9 @@ #ifndef BOOST_HISTOGRAM_DETAIL_META_HPP #define BOOST_HISTOGRAM_DETAIL_META_HPP +#include +#include +#include #include #include #include @@ -19,6 +22,62 @@ namespace boost { namespace histogram { namespace detail { +template +using rm_cvref = typename std::remove_cv::type>::type; + +template +using mp_size = mp11::mp_size>; + +template +using mp_at_c = mp11::mp_at_c, N>; + +template +using copy_qualifiers = mp11::mp_if< + std::is_rvalue_reference, T2&&, + mp11::mp_if, + mp11::mp_if::type>, + const T2&, T2&>, + mp11::mp_if, const T2, T2>>>; + +template +using mp_set_union = mp11::mp_apply_q, L>; + +template +using mp_last = mp11::mp_at_c::value - 1)>; + +template +using container_element_type = mp11::mp_first>; + +template +using iterator_value_type = + typename std::iterator_traits::value_type; + +template +using return_type = typename boost::callable_traits::return_type::type; + +template +using args_type = mp11::mp_if, + mp11::mp_pop_front>, + boost::callable_traits::args_t>; + +template +using arg_type = typename mp11::mp_at_c, N>; + +template +using visitor_return_type = + decltype(std::declval()(std::declval>>())); + +template +constexpr decltype(auto) static_if_c(T&& t, F&& f, Ts&&... ts) { + return std::get<(B ? 0 : 1)>(std::forward_as_tuple( + std::forward(t), std::forward(f)))(std::forward(ts)...); +} + +template +constexpr decltype(auto) static_if(Ts&&... ts) { + return static_if_c(std::forward(ts)...); +} + #define BOOST_HISTOGRAM_MAKE_SFINAE(name, cond) \ template \ struct name##_impl { \ @@ -36,46 +95,60 @@ namespace detail { BOOST_HISTOGRAM_MAKE_SFINAE(has_variance_support, (std::declval().value(), std::declval().variance())); -BOOST_HISTOGRAM_MAKE_SFINAE(has_method_lower, (std::declval().lower(0))); +BOOST_HISTOGRAM_MAKE_SFINAE(has_method_value, (std::declval().value(0))); -BOOST_HISTOGRAM_MAKE_SFINAE(is_dynamic_container, (std::begin(std::declval()))); +BOOST_HISTOGRAM_MAKE_SFINAE( + has_method_options, (static_cast(std::declval().options()))); + +BOOST_HISTOGRAM_MAKE_SFINAE(has_method_metadata, (std::declval().metadata())); + +BOOST_HISTOGRAM_MAKE_SFINAE(is_transform, (&T::forward, &T::inverse)); + +BOOST_HISTOGRAM_MAKE_SFINAE(is_random_access_container, + (std::declval()[0], std::declval().size())); BOOST_HISTOGRAM_MAKE_SFINAE(is_static_container, (std::get<0>(std::declval()))); BOOST_HISTOGRAM_MAKE_SFINAE(is_castable_to_int, (static_cast(std::declval()))); -BOOST_HISTOGRAM_MAKE_SFINAE(is_string, (std::declval().c_str())); +BOOST_HISTOGRAM_MAKE_SFINAE(is_equal_comparable, + (std::declval() == std::declval())); + +BOOST_HISTOGRAM_MAKE_SFINAE(is_axis, (std::declval().size(), &T::operator())); + +BOOST_HISTOGRAM_MAKE_SFINAE(is_iterable, (std::begin(std::declval()), + std::end(std::declval()))); + +BOOST_HISTOGRAM_MAKE_SFINAE(is_streamable, + (std::declval() << std::declval())); + +namespace { +template +struct is_axis_variant_impl : std::false_type {}; + +template +struct is_axis_variant_impl> : std::true_type {}; +} // namespace + +template +using is_axis_variant = typename is_axis_variant_impl::type; + +template +using is_axis_or_axis_variant = mp11::mp_or, is_axis_variant>; + +template +using is_axis_vector = mp11::mp_all, + is_axis_or_axis_variant>>>; struct static_container_tag {}; -struct dynamic_container_tag {}; +struct iterable_container_tag {}; struct no_container_tag {}; template using classify_container = typename std::conditional< - is_static_container::value, static_container_tag, - typename std::conditional<(is_dynamic_container::value && - !std::is_convertible::value && - !is_string::value), - dynamic_container_tag, no_container_tag>::type>::type; - -template ().size(), std::declval().increase(0), - std::declval()[0])> -struct requires_storage {}; - -template (), ++std::declval())> -struct requires_iterator {}; - -template ()[0])> -struct requires_vector {}; - -template (std::declval()))> -struct requires_tuple {}; - -template -using requires_axis = decltype(std::declval().size(), std::declval().shape(), - std::declval().uoflow(), std::declval().label(), - std::declval()[0]); + is_iterable::value, iterable_container_tag, + typename std::conditional::value, static_container_tag, + no_container_tag>::type>::type; namespace { struct bool_mask_impl { @@ -86,7 +159,7 @@ struct bool_mask_impl { b[Int::value] = v; } }; -} +} // namespace template std::vector bool_mask(unsigned n, bool v) { @@ -95,28 +168,40 @@ std::vector bool_mask(unsigned n, bool v) { return b; } -template -using rm_cv_ref = typename std::remove_cv::type>::type; +// poor-mans concept checks +template ().size(), std::declval().increase(0), + std::declval()[0])> +struct requires_storage {}; -template -using mp_size = mp11::mp_size>; +template (), ++std::declval())> +struct requires_iterator {}; -template -using mp_at_c = mp11::mp_at_c, D>; +template , void>> +struct requires_iterable {}; -template -using copy_qualifiers = mp11::mp_if< - std::is_rvalue_reference, T2&&, - mp11::mp_if, - mp11::mp_if::type>, - const T2&, T2&>, - mp11::mp_if, const T2, T2>>>; +template , void>> +struct requires_static_container {}; -template -using mp_set_union = mp11::mp_apply_q, L>; +template , void>> +struct requires_axis {}; -template -using mp_last = mp11::mp_at_c::value - 1)>; +template ::value || is_axis_variant::value), void>> +struct requires_axis_or_axis_variant {}; + +template , + typename = mp11::mp_if_c<(is_random_access_container::value && + (is_axis::value || is_axis_variant::value)), + void>> +struct requires_axis_vector {}; + +template , void>> +struct requires_same {}; + +template , void>> +struct requires_transform {}; } // namespace detail } // namespace histogram diff --git a/include/boost/histogram/histogram.hpp b/include/boost/histogram/histogram.hpp index 95a65821..a6b496d1 100644 --- a/include/boost/histogram/histogram.hpp +++ b/include/boost/histogram/histogram.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -23,13 +24,6 @@ #include #include -// forward declaration for serialization -namespace boost { -namespace serialization { -class access; -} -} - namespace boost { namespace histogram { @@ -42,7 +36,6 @@ public: using storage_type = Storage; using element_type = typename storage_type::element_type; using const_reference = typename storage_type::const_reference; - using scale_type = typename storage_type::scale_type; using const_iterator = iterator_over; histogram() = default; @@ -91,20 +84,18 @@ public: return *this; } - histogram& operator*=(const scale_type& rhs) { - storage_ *= rhs; + histogram& operator*=(const double x) { + storage_ *= x; return *this; } - histogram& operator/=(const scale_type& rhs) { - static_assert(std::is_floating_point::value, - "division requires a floating point type"); - storage_ *= scale_type(1) / rhs; + histogram& operator/=(const double x) { + storage_ *= 1.0 / x; return *this; } /// Number of axes (dimensions) of histogram - std::size_t dim() const noexcept { return detail::axes_size(axes_); } + std::size_t rank() const noexcept { return detail::axes_size(axes_); } /// Total number of bins in the histogram (including underflow/overflow) std::size_t size() const noexcept { return storage_.size(); } @@ -114,34 +105,36 @@ public: /// Get N-th axis (const version) template - auto axis(mp11::mp_size_t) const -> const detail::axis_at& { + decltype(auto) axis(mp11::mp_size_t) const { detail::range_check(axes_); return detail::axis_get(axes_); } /// Get N-th axis template - auto axis(mp11::mp_size_t) -> detail::axis_at& { + decltype(auto) axis(mp11::mp_size_t) { detail::range_check(axes_); return detail::axis_get(axes_); } /// Get first axis (convenience for 1-d histograms, const version) - const detail::axis_at<0, axes_type>& axis() const { return axis(mp11::mp_size_t<0>()); } + decltype(auto) axis() const { return axis(mp11::mp_size_t<0>()); } /// Get first axis (convenience for 1-d histograms) - detail::axis_at<0, axes_type>& axis() { return axis(mp11::mp_size_t<0>()); } + decltype(auto) axis() { return axis(mp11::mp_size_t<0>()); } /// Get N-th axis with runtime index (const version) - template > - const detail::axis_at<0, U>& axis(std::size_t i) const { + template > + decltype(auto) axis(std::size_t i) const { BOOST_ASSERT_MSG(i < axes_.size(), "index out of range"); return axes_[i]; } /// Get N-th axis with runtime index - template > - detail::axis_at<0, U>& axis(std::size_t i) { + template > + decltype(auto) axis(std::size_t i) { BOOST_ASSERT_MSG(i < axes_.size(), "index out of range"); return axes_[i]; } @@ -162,7 +155,7 @@ public: /// Fill histogram with a weight and a value tuple template - void operator()(detail::weight_type&& w, const Ts&... ts) { + void operator()(weight_type&& w, const Ts&... ts) { // case with one argument needs special treatment, specialized below const auto index = detail::call_impl(detail::no_container_tag(), axes_, ts...); if (index) storage_.add(*index, w); @@ -185,7 +178,7 @@ public: } template - void operator()(detail::weight_type&& w, const T& t) { + void operator()(weight_type&& w, const T& t) { // check whether we need to unpack argument const auto index = detail::call_impl(detail::classify_container(), axes_, t); if (index) storage_.add(*index, w); @@ -215,8 +208,8 @@ public: using HR = histogram; auto sub_axes = detail::make_sub_axes(axes_, N(), Ns()...); auto hr = HR(std::move(sub_axes), storage_type(storage_.get_allocator())); - const auto b = detail::bool_mask(dim(), true); - std::vector shape(dim()); + const auto b = detail::bool_mask(rank(), true); + std::vector shape(rank()); for_each_axis(detail::shape_collector(shape.begin())); detail::index_mapper m(shape, b); do { hr.storage_.add(m.second, storage_[m.first]); } while (m.next()); @@ -226,22 +219,22 @@ public: /// Returns a lower-dimensional histogram // precondition: sequence must be strictly ascending axis indices template , + typename = detail::requires_axis_vector, typename = detail::requires_iterator> histogram reduce_to(Iterator begin, Iterator end) const { BOOST_ASSERT_MSG(std::is_sorted(begin, end, std::less_equal()), "integer sequence must be strictly ascending"); - BOOST_ASSERT_MSG(begin == end || static_cast(*(end - 1)) < dim(), + BOOST_ASSERT_MSG(begin == end || static_cast(*(end - 1)) < rank(), "index out of range"); auto sub_axes = histogram::axes_type(axes_.get_allocator()); sub_axes.reserve(std::distance(begin, end)); - auto b = std::vector(dim(), false); + auto b = std::vector(rank(), false); for (auto it = begin; it != end; ++it) { sub_axes.push_back(axes_[*it]); b[*it] = true; } auto hr = histogram(std::move(sub_axes), storage_type(storage_.get_allocator())); - std::vector shape(dim()); + std::vector shape(rank()); for_each_axis(detail::shape_collector(shape.begin())); detail::index_mapper m(shape, b); do { hr.storage_.add(m.second, storage_[m.first]); } while (m.next()); @@ -252,6 +245,9 @@ public: const_iterator end() const noexcept { return const_iterator(*this, size()); } + template + void serialize(Archive&, unsigned); + private: axes_type axes_; Storage storage_; @@ -260,76 +256,69 @@ private: friend class histogram; template friend class iterator_over; - friend class python_access; - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); }; /// static type factory with custom storage type -template -histogram...>, detail::rm_cv_ref> -make_static_histogram_with(Storage&& s, Ts&&... axis) { - using H = histogram...>, detail::rm_cv_ref>; - auto axes = typename H::axes_type(std::forward(axis)...); - return H(std::move(axes), std::forward(s)); +template > +histogram< + std::tuple, detail::rm_cvref...>, + detail::rm_cvref +> +make_histogram_with(S&& s, T&& axis0, Ts&&... axis) { + auto axes = std::make_tuple(std::forward(axis0), std::forward(axis)...); + return histogram>( + std::move(axes), std::forward(s) + ); } /// static type factory with standard storage type -template -histogram...>> make_static_histogram(Ts&&... axis) { - using S = typename histogram...>>::storage_type; - return make_static_histogram_with(S(), std::forward(axis)...); +template > +auto make_histogram(T&& axis0, Ts&&... axis) + -> decltype(make_histogram_with(default_storage(), + std::forward(axis0), + std::forward(axis)...)) +{ + return make_histogram_with(default_storage(), + std::forward(axis0), + std::forward(axis)...); } -namespace detail { -template -using srebind = typename std::allocator_traits< - typename rm_cv_ref::allocator_type>::template rebind_alloc; +/// dynamic type factory from vector-like with custom storage type +template > +histogram, detail::rm_cvref> +make_histogram_with(S&& s, T&& t) { + return histogram, detail::rm_cvref>( + std::forward(t), std::forward(s) + ); } -/// dynamic type factory with custom storage type -template -histogram>, detail::rm_cv_ref> -make_dynamic_histogram_with(Storage&& s, T&& axis0, Ts&&... axis) { - using H = histogram>, - detail::rm_cv_ref>; - auto axes = typename H::axes_type( - {Any(std::forward(axis0)), Any(std::forward(axis))...}, s.get_allocator()); - return H(std::move(axes), std::forward(s)); +/// dynamic type factory from vector-like with standard storage type +template > +auto make_histogram(T&& t) + -> decltype(make_histogram_with(default_storage(), std::forward(t))) +{ + return make_histogram_with(default_storage(), std::forward(t)); } -/// dynamic type factory with standard storage type -template -histogram> make_dynamic_histogram(T&& axis0, Ts&&... axis) { - using S = typename histogram>::storage_type; - return make_dynamic_histogram_with(S(), std::forward(axis0), - std::forward(axis)...); -} - -/// dynamic type factory with custom storage type +/// dynamic type factory from iterator range with custom storage type template > -histogram>, - detail::rm_cv_ref> -make_dynamic_histogram_with(Storage&& s, Iterator begin, Iterator end) { - using H = - histogram>, - detail::rm_cv_ref>; - auto axes = typename H::axes_type(s.get_allocator()); - axes.reserve(std::distance(begin, end)); - while (begin != end) axes.emplace_back(*begin++); - return H(std::move(axes), std::forward(s)); +histogram>, + detail::rm_cvref> +make_histogram_with(Storage&& s, Iterator begin, Iterator end) { + using T = detail::iterator_value_type; + auto axes = std::vector(begin, end); + return make_histogram_with(std::forward(s), std::move(axes)); } -/// dynamic type factory with standard storage type +/// dynamic type factory from iterator range with standard storage type template > -histogram> make_dynamic_histogram( - Iterator begin, Iterator end) { - using S = typename histogram>::storage_type; - return make_dynamic_histogram_with(S(), begin, end); +auto make_histogram(Iterator begin, Iterator end) + -> decltype(make_histogram_with(default_storage(), begin, end)) +{ + return make_histogram_with(default_storage(), begin, end); } } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/histogram_fwd.hpp b/include/boost/histogram/histogram_fwd.hpp index 2a67158a..eee3070c 100644 --- a/include/boost/histogram/histogram_fwd.hpp +++ b/include/boost/histogram/histogram_fwd.hpp @@ -8,54 +8,62 @@ #define BOOST_HISTOGRAM_HISTOGRAM_FWD_HPP #include // for std::allocator -#include #include namespace boost { namespace histogram { - namespace axis { +struct empty_metadata_type {}; + +enum class option_type { + none = 0, + overflow = 1, + underflow_and_overflow = 2, +}; namespace transform { +template struct identity; +template struct log; +template struct sqrt; +template struct pow; } // namespace transform -template > +template , + typename MetaData = std::string> class regular; -template > + +template class circular; -template > + +template , + typename MetaData = std::string> class variable; -template > + +template class integer; -template > + +template , + typename MetaData = std::string> class category; template -class any; -using any_std = - any>, - regular>, - regular>, - regular>, - circular>, variable>, - integer>, category>, - category>>; - +class variant; } // namespace axis template > struct adaptive_storage; -template > + +template > struct array_storage; -template , class Storage = adaptive_storage<>> -class histogram; +using default_storage = adaptive_storage<>; +template +class histogram; } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/iterator.hpp b/include/boost/histogram/iterator.hpp index 73696f8b..18905676 100644 --- a/include/boost/histogram/iterator.hpp +++ b/include/boost/histogram/iterator.hpp @@ -32,7 +32,7 @@ public: cache_.reset(); } - std::size_t dim() const noexcept { return histogram_.dim(); } + std::size_t rank() const noexcept { return histogram_.rank(); } int idx(std::size_t dim = 0) const noexcept { if (!cache_) { cache_.set(histogram_); } diff --git a/include/boost/histogram/ostream_operators.hpp b/include/boost/histogram/ostream_operators.hpp index c54e9f7a..a42de867 100644 --- a/include/boost/histogram/ostream_operators.hpp +++ b/include/boost/histogram/ostream_operators.hpp @@ -20,8 +20,8 @@ template struct axis_ostream_visitor { OStream& os_; explicit axis_ostream_visitor(OStream& os) : os_(os) {} - template - void operator()(const Axis& a) const { + template + void operator()(const T& a) const { os_ << "\n " << a << ","; } }; @@ -33,7 +33,7 @@ std::basic_ostream& operator<<(std::basic_ostream& using OS = std::basic_ostream; os << "histogram("; h.for_each_axis(detail::axis_ostream_visitor(os)); - os << (h.dim() ? "\n)" : ")"); + os << (h.rank() ? "\n)" : ")"); return os; } diff --git a/include/boost/histogram/serialization.hpp b/include/boost/histogram/serialization.hpp index 05812313..9a37c34a 100644 --- a/include/boost/histogram/serialization.hpp +++ b/include/boost/histogram/serialization.hpp @@ -7,40 +7,53 @@ #ifndef BOOST_HISTOGRAM_SERIALIZATION_HPP #define BOOST_HISTOGRAM_SERIALIZATION_HPP -#include -#include #include -#include +#include +#include +#include +#include +#include +#include #include -#include #include #include #include #include +#include #include -#include +#include #include #include +#include +#include /** \file boost/histogram/serialization.hpp * \brief Defines the serialization functions, to use with boost.serialize. * */ +namespace std { +template +void serialize(Archive& ar, tuple& t, unsigned /* version */) { + ::boost::mp11::tuple_for_each(t, [&ar](auto& x) { ar& x; }); +} +} // namespace std + namespace boost { namespace histogram { +template +template +void weight_counter::serialize(Archive& ar, unsigned /* version */) { + ar& w; + ar& w2; +} + +template +void serialize(Archive& ar, array_storage& s, unsigned /* version */) { + ar& s.buffer; +} namespace detail { -template -struct serialize_t { - Archive& ar_; - explicit serialize_t(Archive& ar) : ar_(ar) {} - template - void operator()(T& t) const { - ar_& t; - } -}; - struct serializer { template void operator()(T*, Buffer& b, Archive& ar) { @@ -56,21 +69,8 @@ struct serializer { if (Archive::is_loading::value) { b.ptr = nullptr; } } }; - } // namespace detail -template -template -void weight_counter::serialize(Archive& ar, unsigned /* version */) { - ar& w; - ar& w2; -} - -template -void serialize(Archive& ar, array_storage& s, unsigned /* version */) { - ar& s.buffer; -} - template void serialize(Archive& ar, adaptive_storage& s, unsigned /* version */) { using S = adaptive_storage; @@ -80,113 +80,85 @@ void serialize(Archive& ar, adaptive_storage& s, unsigned /* version */) { S::apply(detail::serializer(), s.buffer, ar); } +template +template +void histogram::serialize(Archive& ar, unsigned /* version */) { + ar& axes_; + ar& storage_; +} + namespace axis { - template -void base::serialize(Archive& ar, unsigned /* version */) { - ar& size_; - ar& shape_; +void serialize(Archive&, empty_metadata_type&, unsigned /* version */) {} // noop + +template +template +void base::serialize(Archive& ar, unsigned /* version */) { + ar& size_meta_.first(); + ar& size_meta_.second(); + ar& opt_; } -template +template template -void labeled_base::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object(*this); - auto size = label_.size(); - ar& size; - if (Archive::is_loading::value) { label_.resize(size); } - ar& serialization::make_array(label_.data(), size); -} - -namespace transform { -template -void pow::serialize(Archive& ar, unsigned /* version */) { +void transform::pow::serialize(Archive& ar, unsigned /* version */) { ar& power; } -} // namespace transform -template +template template -void regular::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object>(*this); - ar& boost::serialization::base_object(*this); +void regular::serialize(Archive& ar, unsigned /* version */) { + ar& static_cast(*this); + ar& static_cast(*this); ar& min_; ar& delta_; } -template +template template -void circular::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object>(*this); +void circular::serialize(Archive& ar, unsigned /* version */) { + ar& static_cast(*this); ar& phase_; - ar& perimeter_; + ar& delta_; } -template +template template -void variable::serialize(Archive& ar, unsigned /* version */) { - if (Archive::is_loading::value) { this->~variable(); } - - ar& boost::serialization::base_object>(*this); - - if (Archive::is_loading::value) { - value_allocator_type a(base_type::get_allocator()); - x_ = boost::histogram::detail::create_buffer(a, nx()); - } - - ar& boost::serialization::make_array(x_, base_type::size() + 1); +void variable::serialize(Archive& ar, unsigned /* version */) { + // destroy must happen before base serialization with old size + if (Archive::is_loading::value) detail::destroy_buffer(x_.second(), x_.first(), nx()); + ar& static_cast(*this); + if (Archive::is_loading::value) + x_.first() = boost::histogram::detail::create_buffer(x_.second(), nx()); + ar& boost::serialization::make_array(x_.first(), nx()); } -template +template template -void integer::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object>(*this); +void integer::serialize(Archive& ar, unsigned /* version */) { + ar& static_cast(*this); ar& min_; } -template +template template -void category::serialize(Archive& ar, unsigned /* version */) { - if (Archive::is_loading::value) { this->~category(); } - - ar& boost::serialization::base_object>(*this); - - if (Archive::is_loading::value) { - value_allocator_type a(base_type::get_allocator()); - x_ = boost::histogram::detail::create_buffer(a, nx()); - } - - ar& boost::serialization::make_array(x_, base_type::size()); +void category::serialize(Archive& ar, unsigned /* version */) { + // destroy must happen before base serialization with old size + if (Archive::is_loading::value) + detail::destroy_buffer(x_.second(), x_.first(), base_type::size()); + ar& static_cast(*this); + if (Archive::is_loading::value) + x_.first() = boost::histogram::detail::create_buffer(x_.second(), base_type::size()); + ar& boost::serialization::make_array(x_.first(), base_type::size()); } template template -void any::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object>(*this); +void variant::serialize(Archive& ar, unsigned /* version */) { + ar& static_cast(*this); } - } // namespace axis -namespace { -template -void serialize_axes(Archive& ar, std::tuple& axes) { - detail::serialize_t sh(ar); - mp11::tuple_for_each(axes, sh); -} - -template -void serialize_axes(Archive& ar, std::vector& axes) { - ar& axes; -} -} - -template -template -void histogram::serialize(Archive& ar, unsigned /* version */) { - serialize_axes(ar, axes_); - ar& storage_; -} - } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/storage/adaptive_storage.hpp b/include/boost/histogram/storage/adaptive_storage.hpp index 94b603c4..880ac3d1 100644 --- a/include/boost/histogram/storage/adaptive_storage.hpp +++ b/include/boost/histogram/storage/adaptive_storage.hpp @@ -9,15 +9,21 @@ #include #include -#include #include #include #include #include -#include #include -#include #include +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif +// warning-ignore required in Boost-1.66 for cpp_int.hpp:822 +#include +#ifdef __clang__ +#pragma clang diagnostic pop +#endif #include #include #include @@ -45,8 +51,36 @@ bool safe_assign(T& t, const U& u) { return true; } +template +struct make_unsigned_impl; + +template +struct make_unsigned_impl { + using type = typename std::make_unsigned::type; +}; + +template +struct make_unsigned_impl { + using type = T; +}; + +template +using make_unsigned = + typename make_unsigned_impl::type>::type; + template bool safe_radd(T& t, const U& u) { + BOOST_ASSERT(t >= 0); + BOOST_ASSERT(u >= 0); + using V = make_unsigned; + // static_cast converts back from signed to unsigned integer + if (static_cast(std::numeric_limits::max() - t) < static_cast(u)) return false; + t += static_cast(u); // static_cast to suppress conversion warning + return true; +} + +template +bool safe_radd(T& t, const boost::multiprecision::number& u) { BOOST_ASSERT(t >= 0); BOOST_ASSERT(u >= 0); // static_cast converts back from signed to unsigned integer @@ -54,7 +88,7 @@ bool safe_radd(T& t, const U& u) { t += static_cast(u); // static_cast to suppress conversion warning return true; } -} +} // namespace detail template struct adaptive_storage { @@ -64,23 +98,16 @@ struct adaptive_storage { "adaptive_storage requires allocator with trivial pointer type"); using allocator_type = Allocator; - using element_type = weight_counter; - using scale_type = double; - using const_reference = element_type; + using element_type = double; + using const_reference = double; - using wcount = weight_counter; - using mp_int = boost::multiprecision::number< - boost::multiprecision::cpp_int_backend< - 0, 0, boost::multiprecision::signed_magnitude, - boost::multiprecision::unchecked, + using mp_int = boost::multiprecision::number::template rebind_alloc< - boost::multiprecision::limb_type - > - > - >; + boost::multiprecision::limb_type>>>; - using types = mp11::mp_list; + using types = + mp11::mp_list; template static constexpr char type_index() { @@ -92,27 +119,28 @@ struct adaptive_storage { char type; std::size_t size; void* ptr; + buffer_type(std::size_t s = 0, const allocator_type& a = allocator_type()) : alloc(a), type(0), size(s), ptr(nullptr) {} template T* create_impl(T*, const U* init) { - using alloc_type = typename std::allocator_traits< - allocator_type>::template rebind_alloc; + using alloc_type = + typename std::allocator_traits::template rebind_alloc; alloc_type a(alloc); // rebind allocator - return init ? detail::create_buffer_from_iter(a, size, init) : - detail::create_buffer(a, size, 0); + return init ? detail::create_buffer_from_iter(a, size, init) + : detail::create_buffer(a, size, 0); } template mp_int* create_impl(mp_int*, const U* init) { - using alloc_type = typename std::allocator_traits< - allocator_type>::template rebind_alloc; + using alloc_type = + typename std::allocator_traits::template rebind_alloc; alloc_type a(alloc); // rebound allocator for buffer // mp_int has no ctor with an allocator instance, cannot pass state :( // typename mp_int::backend_type::allocator_type a2(alloc); - return init ? detail::create_buffer_from_iter(a, size, init) : - detail::create_buffer(a, size, 0); + return init ? detail::create_buffer_from_iter(a, size, init) + : detail::create_buffer(a, size, 0); } void* create_impl(void*, const void* init) { @@ -133,11 +161,29 @@ struct adaptive_storage { } }; + template + static decltype(auto) apply(F&& f, B&& b, Ts&&... ts) { + // this is intentionally not a switch, the if-chain is faster in benchmarks + if (b.type == type_index()) + return f(reinterpret_cast(b.ptr), b, std::forward(ts)...); + if (b.type == type_index()) + return f(reinterpret_cast(b.ptr), b, std::forward(ts)...); + if (b.type == type_index()) + return f(reinterpret_cast(b.ptr), b, std::forward(ts)...); + if (b.type == type_index()) + return f(reinterpret_cast(b.ptr), b, std::forward(ts)...); + if (b.type == type_index()) + return f(reinterpret_cast(b.ptr), b, std::forward(ts)...); + if (b.type == type_index()) + return f(reinterpret_cast(b.ptr), b, std::forward(ts)...); + // b.type == 0 is intentionally the last in the chain, + // because it is rarely triggered + return f(b.ptr, b, std::forward(ts)...); + } + ~adaptive_storage() { apply(destroyer(), buffer); } - adaptive_storage(const adaptive_storage& o) { - apply(replacer(), o.buffer, buffer); - } + adaptive_storage(const adaptive_storage& o) { apply(replacer(), o.buffer, buffer); } adaptive_storage& operator=(const adaptive_storage& o) { if (this != &o) { apply(replacer(), o.buffer, buffer); } @@ -157,8 +203,8 @@ struct adaptive_storage { template > explicit adaptive_storage(const S& s) : buffer(s.size(), s.get_allocator()) { - buffer.set(buffer.template create()); - auto it = static_cast(buffer.ptr); + buffer.set(buffer.template create()); + auto it = static_cast(buffer.ptr); const auto end = it + size(); std::size_t i = 0; while (it != end) *it++ = s[i++]; @@ -200,10 +246,14 @@ struct adaptive_storage { apply(adder(), buffer, i, x); } - const_reference operator[](std::size_t i) const { - return apply(getter(), buffer, i); + template + void add(std::size_t i, const weight_type& x) { + BOOST_ASSERT(i < size()); + apply(adder(), buffer, i, x.value); } + const_reference operator[](std::size_t i) const { return apply(getter(), buffer, i); } + bool operator==(const adaptive_storage& o) const { if (size() != o.size()) return false; return apply(comparer(), buffer, o.buffer); @@ -251,8 +301,8 @@ struct adaptive_storage { struct destroyer { template void operator()(T* tp, Buffer& b) { - using alloc_type = typename std::allocator_traits< - allocator_type>::template rebind_alloc; + using alloc_type = + typename std::allocator_traits::template rebind_alloc; alloc_type a(b.alloc); // rebind allocator detail::destroy_buffer(a, tp, b.size); } @@ -261,32 +311,6 @@ struct adaptive_storage { void operator()(void*, Buffer&) {} }; - template - static typename std::result_of::type apply(F&& f, B&& b, Ts&&... ts) { - // this is intentionally not a switch, the if-chain is faster in benchmarks - if (b.type == type_index()) - return f(reinterpret_cast(b.ptr), std::forward(b), - std::forward(ts)...); - if (b.type == type_index()) - return f(reinterpret_cast(b.ptr), std::forward(b), - std::forward(ts)...); - if (b.type == type_index()) - return f(reinterpret_cast(b.ptr), std::forward(b), - std::forward(ts)...); - if (b.type == type_index()) - return f(reinterpret_cast(b.ptr), std::forward(b), - std::forward(ts)...); - if (b.type == type_index()) - return f(reinterpret_cast(b.ptr), std::forward(b), - std::forward(ts)...); - if (b.type == type_index()) - return f(reinterpret_cast(b.ptr), std::forward(b), - std::forward(ts)...); - // b.type == 0 is intentionally the last in the chain, because it is rarely - // triggered - return f(b.ptr, std::forward(b), std::forward(ts)...); - } - struct replacer { template void operator()(T* optr, const OBuffer& ob, Buffer& b) { @@ -333,14 +357,15 @@ struct adaptive_storage { } template - void operator()(wcount* tp, Buffer&, std::size_t i) { + void operator()(double* tp, Buffer&, std::size_t i) { ++tp[i]; } }; struct adder { template - void if_U_is_integral(std::true_type, mp_int* tp, Buffer&, std::size_t i, const U& x) { + void if_U_is_integral(std::true_type, mp_int* tp, Buffer&, std::size_t i, + const U& x) { tp[i] += static_cast(x); } @@ -357,17 +382,17 @@ struct adaptive_storage { template void if_U_is_integral(std::false_type, T* tp, Buffer& b, std::size_t i, const U& x) { - auto ptr = b.template create(tp); + auto ptr = b.template create(tp); destroyer()(tp, b); b.set(ptr); - operator()(static_cast(b.ptr), b, i, x); + operator()(static_cast(b.ptr), b, i, x); } template void operator()(T* tp, Buffer& b, std::size_t i, const U& x) { - if_U_is_integral(std::integral_constant::value || - std::is_same::value)>(), tp, b, i, x); + if_U_is_integral( + mp11::mp_bool<(std::is_integral::value || std::is_same::value)>(), + tp, b, i, x); } template @@ -378,12 +403,12 @@ struct adaptive_storage { } template - void operator()(wcount* tp, Buffer&, std::size_t i, const U& x) { + void operator()(double* tp, Buffer&, std::size_t i, const U& x) { tp[i] += x; } template - void operator()(wcount* tp, Buffer&, std::size_t i, const mp_int& x) { + void operator()(double* tp, Buffer&, std::size_t i, const mp_int& x) { tp[i] += static_cast(x); } }; @@ -400,22 +425,35 @@ struct adaptive_storage { struct getter { template - wcount operator()(T* tp, Buffer&, std::size_t i) { - return static_cast(tp[i]); + double operator()(T* tp, Buffer&, std::size_t i) { + return static_cast(tp[i]); } template - wcount operator()(void*, Buffer&, std::size_t) { - return static_cast(0); + double operator()(void*, Buffer&, std::size_t) { + return 0.0; } }; // precondition: buffers already have same size struct comparer { struct inner { + struct cmp { + template + bool operator()(const T& t, const U& u) { + return t == u; + } + bool operator()(const mp_int& t, const double& u) { + return static_cast(t) == u; + } + bool operator()(const double& t, const mp_int& u) { + return t == static_cast(u); + } + }; + template bool operator()(const U* optr, const OBuffer& ob, const T* tp) { - return std::equal(optr, optr + ob.size, tp); + return std::equal(optr, optr + ob.size, tp, cmp()); } template @@ -444,17 +482,25 @@ struct adaptive_storage { struct multiplier { template void operator()(T* tp, Buffer& b, const double x) { - auto ptr = b.template create(tp); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) // possible loss of data +#endif + // potential lossy conversion that cannot be avoided + auto ptr = b.template create(tp); +#ifdef _MSC_VER +#pragma warning(pop) +#endif destroyer()(tp, b); b.set(ptr); - operator()(reinterpret_cast(b.ptr), b, x); + operator()(reinterpret_cast(b.ptr), b, x); } template void operator()(void*, Buffer&, const double) {} template - void operator()(wcount* tp, Buffer& b, const double x) { + void operator()(double* tp, Buffer& b, const double x) { for (auto end = tp + b.size; tp != end; ++tp) *tp *= x; } }; diff --git a/include/boost/histogram/storage/array_storage.hpp b/include/boost/histogram/storage/array_storage.hpp index d621ffb5..77a6ecb5 100644 --- a/include/boost/histogram/storage/array_storage.hpp +++ b/include/boost/histogram/storage/array_storage.hpp @@ -18,11 +18,10 @@ namespace boost { namespace histogram { -template +template struct array_storage { using allocator_type = Allocator; using element_type = T; - using scale_type = ScaleType; using const_reference = const T&; using buffer_type = std::vector; @@ -90,7 +89,7 @@ struct array_storage { return *this; } - array_storage& operator*=(const scale_type& x) noexcept { + array_storage& operator*=(const double x) noexcept { for (std::size_t i = 0; i < size(); ++i) buffer[i] *= x; return *this; } diff --git a/include/boost/histogram/storage/weight_counter.hpp b/include/boost/histogram/storage/weight_counter.hpp index 55c4127e..ab80dd5a 100644 --- a/include/boost/histogram/storage/weight_counter.hpp +++ b/include/boost/histogram/storage/weight_counter.hpp @@ -11,15 +11,10 @@ #include namespace boost { - -namespace serialization { -class access; -} // namespace serialization - namespace histogram { /// Double counter which holds a sum of weights and a sum of squared weights -template +template class weight_counter { public: /// Beware: For performance reasons counters are not initialized @@ -30,8 +25,7 @@ public: weight_counter& operator=(weight_counter&&) = default; weight_counter(const RealType& value, const RealType& variance) noexcept - : w(value), - w2(variance) {} + : w(value), w2(variance) {} explicit weight_counter(const RealType& value) noexcept : w(value), w2(value) {} @@ -56,7 +50,7 @@ public: } template - weight_counter& operator+=(const detail::weight_type& rhs) { + weight_counter& operator+=(const weight_type& rhs) { const auto x = static_cast(rhs.value); w += x; w2 += x * x; @@ -105,12 +99,10 @@ public: return static_cast(w); } -private: - friend class ::boost::serialization::access; - template void serialize(Archive&, unsigned /* version */); +private: RealType w, w2; }; diff --git a/include/boost/histogram/weight.hpp b/include/boost/histogram/weight.hpp index 50baf61e..c070c6fe 100644 --- a/include/boost/histogram/weight.hpp +++ b/include/boost/histogram/weight.hpp @@ -7,31 +7,23 @@ #ifndef BOOST_HISTOGRAM_WEIGHT_HPP #define BOOST_HISTOGRAM_WEIGHT_HPP +#include + namespace boost { namespace histogram { -namespace detail { + +/// Type wrapper to make T as weight template struct weight_type { T value; }; -template -struct sample_type { - T value; -}; -} // namespace detail - /// Helper function to mark argument as a weight template -detail::weight_type weight(T&& t) { - return {t}; +weight_type weight(T&& t) { + return {std::forward(t)}; } -/// Helper function to mark argument as a sample -template -detail::sample_type sample(T&& t) { - return {t}; -} } } diff --git a/test/Jamfile b/test/Jamfile index 2183d442..9b105292 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -15,28 +15,30 @@ project histogram-test alias run-tests : [ run adaptive_storage_serialization_test.cpp /boost/serialization//boost_serialization/static ] - [ run histogram_serialization_test.cpp /boost/serialization//boost_serialization/static ] [ run adaptive_storage_test.cpp ] [ run array_storage_test.cpp ] - [ run axis_test.cpp ] + [ run axis_regular_test.cpp ] + [ run axis_circular_test.cpp ] + [ run axis_variable_test.cpp ] + [ run axis_integer_test.cpp ] + [ run axis_category_test.cpp ] + [ run axis_variant_test.cpp ] [ run detail_test.cpp ] [ run histogram_dynamic_test.cpp ] [ run histogram_mixed_test.cpp ] + [ run histogram_serialization_test.cpp /boost/serialization//boost_serialization/static ] [ run histogram_test.cpp ] [ run index_mapper_test.cpp ] [ run meta_test.cpp ] - [ link speed_cpp.cpp : : speed_test ] [ run utility_test.cpp ] [ run weight_counter_test.cpp ] ; alias run-fail-tests : - [ run-fail histogram_dynamic_add_fail.cpp ] [ run-fail histogram_dynamic_at_tuple_wrong_dimension_fail.cpp ] [ run-fail histogram_dynamic_at_vector_wrong_dimension_fail.cpp ] [ run-fail histogram_dynamic_at_wrong_dimension_fail.cpp ] [ run-fail histogram_dynamic_reduce_wrong_order_fail.cpp ] - [ run-fail histogram_static_add_fail.cpp ] [ run-fail histogram_static_at_vector_wrong_dimension_fail.cpp ] ; @@ -46,4 +48,5 @@ alias run-speed-tests : [ run speed_root.cpp ] ; +explicit run-fail-tests ; explicit run-speed-tests ; diff --git a/test/adaptive_storage_serialization_test.cpp b/test/adaptive_storage_serialization_test.cpp index 00263501..d0140131 100644 --- a/test/adaptive_storage_serialization_test.cpp +++ b/test/adaptive_storage_serialization_test.cpp @@ -82,7 +82,7 @@ int main() { serialization_impl(); serialization_impl(); serialization_impl(); - serialization_impl(); + serialization_impl(); } return boost::report_errors(); diff --git a/test/adaptive_storage_test.cpp b/test/adaptive_storage_test.cpp index 7ff9276e..b5af774f 100644 --- a/test/adaptive_storage_test.cpp +++ b/test/adaptive_storage_test.cpp @@ -15,7 +15,7 @@ namespace bh = boost::histogram; using adaptive_storage_type = bh::adaptive_storage>; -template using array_storage = bh::array_storage>; +template using array_storage = bh::array_storage>; using bh::weight; template @@ -50,31 +50,22 @@ void copy_impl() { } template -void equal_impl() { +void equal_1_impl() { auto a = prepare(1); auto b = prepare(1, T(0)); - BOOST_TEST_EQ(a[0].value(), 0.0); - BOOST_TEST_EQ(a[0].variance(), 0.0); + BOOST_TEST_EQ(a[0], 0.0); BOOST_TEST(a == b); b.increase(0); BOOST_TEST(!(a == b)); - - array_storage c; - c.reset(1); - auto d = prepare(1, T(0)); - BOOST_TEST(c == d); - c.increase(0); - BOOST_TEST(!(c == d)); } template <> -void equal_impl() { +void equal_1_impl() { auto a = prepare(1); auto b = prepare(1, 0); auto c = prepare(2, 0); auto d = prepare(1); - BOOST_TEST_EQ(a[0].value(), 0.0); - BOOST_TEST_EQ(a[0].variance(), 0.0); + BOOST_TEST_EQ(a[0], 0.0); BOOST_TEST(a == b); BOOST_TEST(b == a); BOOST_TEST(a == d); @@ -89,6 +80,16 @@ void equal_impl() { BOOST_TEST(!(d == a)); } +template +void equal_2_impl() { + auto a = prepare(1); + array_storage b; + b.reset(1); + BOOST_TEST(a == b); + b.increase(0); + BOOST_TEST(!(a == b)); +} + template void increase_and_grow_impl() { auto tmax = std::numeric_limits::max(); @@ -100,24 +101,24 @@ void increase_and_grow_impl() { auto x = prepare(2); x.increase(0); - n2.add(0, x[0].value()); + n2.add(0, x[0]); double v = tmax; ++v; - BOOST_TEST_EQ(n[0].value(), v); - BOOST_TEST_EQ(n2[0].value(), v); - BOOST_TEST_EQ(n[1].value(), 0.0); - BOOST_TEST_EQ(n2[1].value(), 0.0); + BOOST_TEST_EQ(n[0], v); + BOOST_TEST_EQ(n2[0], v); + BOOST_TEST_EQ(n[1], 0.0); + BOOST_TEST_EQ(n2[1], 0.0); } template <> void increase_and_grow_impl() { auto s = prepare(2); - BOOST_TEST_EQ(s[0].value(), 0); - BOOST_TEST_EQ(s[1].value(), 0); + BOOST_TEST_EQ(s[0], 0); + BOOST_TEST_EQ(s[1], 0); s.increase(0); - BOOST_TEST_EQ(s[0].value(), 1); - BOOST_TEST_EQ(s[1].value(), 0); + BOOST_TEST_EQ(s[0], 1); + BOOST_TEST_EQ(s[1], 0); } template @@ -129,20 +130,20 @@ void convert_array_storage_impl() { auto a = aref; a = s; - BOOST_TEST_EQ(a[0].value(), 1.0); + BOOST_TEST_EQ(a[0], 1.0); BOOST_TEST(a == s); a.increase(0); BOOST_TEST(!(a == s)); adaptive_storage_type b(s); - BOOST_TEST_EQ(b[0].value(), 1.0); + BOOST_TEST_EQ(b[0], 1.0); BOOST_TEST(b == s); b.increase(0); BOOST_TEST(!(b == s)); auto c = aref; c.add(0, s[0]); - BOOST_TEST_EQ(c[0].value(), 1.0); + BOOST_TEST_EQ(c[0], 1.0); BOOST_TEST(c == s); BOOST_TEST(s == c); @@ -156,20 +157,20 @@ void convert_array_storage_impl() { auto e = aref; e = s; - BOOST_TEST_EQ(e[0].value(), 1.0); + BOOST_TEST_EQ(e[0], 1.0); BOOST_TEST(e == s); e.increase(0); BOOST_TEST(!(e == s)); adaptive_storage_type f(s); - BOOST_TEST_EQ(f[0].value(), 1.0); + BOOST_TEST_EQ(f[0], 1.0); BOOST_TEST(f == s); f.increase(0); BOOST_TEST(!(f == s)); auto g = aref; g.add(0, s[0]); - BOOST_TEST_EQ(g[0].value(), 1.0); + BOOST_TEST_EQ(g[0], 1.0); BOOST_TEST(g == s); BOOST_TEST(s == g); @@ -185,21 +186,21 @@ void convert_array_storage_impl() { template <> void convert_array_storage_impl() { const auto aref = prepare(1); - BOOST_TEST_EQ(aref[0].value(), 0.0); + BOOST_TEST_EQ(aref[0], 0.0); array_storage s; s.reset(1); s.increase(0); auto a = aref; a = s; - BOOST_TEST_EQ(a[0].value(), 1.0); + BOOST_TEST_EQ(a[0], 1.0); BOOST_TEST(a == s); a.increase(0); BOOST_TEST(!(a == s)); auto c = aref; c.add(0, s[0]); - BOOST_TEST_EQ(c[0].value(), 1.0); + BOOST_TEST_EQ(c[0], 1.0); BOOST_TEST(c == s); BOOST_TEST(s == c); @@ -216,15 +217,14 @@ void add_impl() { auto b = prepare(2); if (std::is_same::value) { a += b; - BOOST_TEST_EQ(a[0].value(), 0); - BOOST_TEST_EQ(a[1].value(), 0); + BOOST_TEST_EQ(a[0], 0); + BOOST_TEST_EQ(a[1], 0); } else { b.increase(0); b.increase(0); a += b; - BOOST_TEST_EQ(a[0].value(), 2); - BOOST_TEST_EQ(a[0].variance(), 2); - BOOST_TEST_EQ(a[1].value(), 0); + BOOST_TEST_EQ(a[0], 2); + BOOST_TEST_EQ(a[1], 0); } } @@ -236,7 +236,7 @@ void add_impl_all_rhs() { add_impl(); add_impl(); add_impl(); - add_impl(); + add_impl(); } int main() { @@ -270,7 +270,7 @@ int main() { // copy { - copy_impl(); + copy_impl(); copy_impl(); copy_impl(); copy_impl(); @@ -281,13 +281,29 @@ int main() { // equal_operator { - equal_impl(); - equal_impl(); - equal_impl(); - equal_impl(); - equal_impl(); - equal_impl(); - equal_impl(); + equal_1_impl(); + equal_1_impl(); + equal_1_impl(); + equal_1_impl(); + equal_1_impl(); + equal_1_impl(); + equal_1_impl(); + + equal_2_impl(); + equal_2_impl(); + equal_2_impl(); + equal_2_impl(); + equal_2_impl(); + equal_2_impl(); + equal_2_impl(); + + equal_2_impl(); + + auto a = prepare(1); + auto b = prepare(1); + BOOST_TEST(a == b); + a.increase(0); + BOOST_TEST_NOT(a == b); } // increase_and_grow @@ -300,11 +316,11 @@ int main() { // only increase for mp_int auto a = prepare(2, 1); - BOOST_TEST_EQ(a[0].value(), 1); - BOOST_TEST_EQ(a[1].value(), 0); + BOOST_TEST_EQ(a[0], 1); + BOOST_TEST_EQ(a[1], 0); a.increase(0); - BOOST_TEST_EQ(a[0].value(), 2); - BOOST_TEST_EQ(a[1].value(), 0); + BOOST_TEST_EQ(a[0], 2); + BOOST_TEST_EQ(a[1], 0); } // add @@ -315,38 +331,33 @@ int main() { add_impl_all_rhs(); add_impl_all_rhs(); add_impl_all_rhs(); - add_impl_all_rhs(); + add_impl_all_rhs(); } // add_and_grow { auto a = prepare(1); a += a; - BOOST_TEST_EQ(a[0].value(), 0); + BOOST_TEST_EQ(a[0], 0); a.increase(0); double x = 1; auto b = prepare(1); b.increase(0); - BOOST_TEST_EQ(b[0].value(), x); + BOOST_TEST_EQ(b[0], x); for (unsigned i = 0; i < 80; ++i) { x += x; - a.add(0, a[0].value()); + a.add(0, a[0]); b += b; - BOOST_TEST_EQ(a[0].value(), x); - BOOST_TEST_EQ(a[0].variance(), x); - BOOST_TEST_EQ(b[0].value(), x); - BOOST_TEST_EQ(b[0].variance(), x); + BOOST_TEST_EQ(a[0], x); + BOOST_TEST_EQ(b[0], x); auto c = prepare(1); - c.add(0, a[0].value()); - BOOST_TEST_EQ(c[0].value(), x); - BOOST_TEST_EQ(c[0].variance(), x); + c.add(0, a[0]); + BOOST_TEST_EQ(c[0], x); c.add(0, weight(0)); - BOOST_TEST_EQ(c[0].value(), x); - BOOST_TEST_EQ(c[0].variance(), x); + BOOST_TEST_EQ(c[0], x); auto d = prepare(1); d.add(0, weight(x)); - BOOST_TEST_EQ(d[0].value(), x); - BOOST_TEST_EQ(d[0].variance(), x * x); + BOOST_TEST_EQ(d[0], x); } } @@ -354,24 +365,18 @@ int main() { { auto a = prepare(2); a *= 2; - BOOST_TEST_EQ(a[0].value(), 0); - BOOST_TEST_EQ(a[1].value(), 0); + BOOST_TEST_EQ(a[0], 0); + BOOST_TEST_EQ(a[1], 0); a.increase(0); a *= 3; - BOOST_TEST_EQ(a[0].value(), 3); - BOOST_TEST_EQ(a[0].variance(), 9); - BOOST_TEST_EQ(a[1].value(), 0); - BOOST_TEST_EQ(a[1].variance(), 0); - a.add(1, adaptive_storage_type::element_type(2, 5)); - BOOST_TEST_EQ(a[0].value(), 3); - BOOST_TEST_EQ(a[0].variance(), 9); - BOOST_TEST_EQ(a[1].value(), 2); - BOOST_TEST_EQ(a[1].variance(), 5); + BOOST_TEST_EQ(a[0], 3); + BOOST_TEST_EQ(a[1], 0); + a.add(1, 2); + BOOST_TEST_EQ(a[0], 3); + BOOST_TEST_EQ(a[1], 2); a *= 3; - BOOST_TEST_EQ(a[0].value(), 9); - BOOST_TEST_EQ(a[0].variance(), 81); - BOOST_TEST_EQ(a[1].value(), 6); - BOOST_TEST_EQ(a[1].variance(), 45); + BOOST_TEST_EQ(a[0], 9); + BOOST_TEST_EQ(a[1], 6); } // convert_array_storage @@ -382,7 +387,7 @@ int main() { convert_array_storage_impl(); convert_array_storage_impl(); convert_array_storage_impl(); - convert_array_storage_impl(); + convert_array_storage_impl(); } return boost::report_errors(); diff --git a/test/axis_category_test.cpp b/test/axis_category_test.cpp new file mode 100644 index 00000000..6a3c15b5 --- /dev/null +++ b/test/axis_category_test.cpp @@ -0,0 +1,65 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +int main() { + // bad_ctors + { + auto empty = std::vector(0); + BOOST_TEST_THROWS((axis::category<>(empty)), std::invalid_argument); + } + + // axis::category + { + std::string A("A"), B("B"), C("C"), other; + axis::category a({A, B, C}); + axis::category b; + BOOST_TEST_NE(a, b); + b = a; + BOOST_TEST_EQ(a, b); + b = axis::category{{B, A, C}}; + BOOST_TEST_NE(a, b); + b = a; + b = b; + BOOST_TEST_EQ(a, b); + axis::category c = std::move(b); + BOOST_TEST_EQ(c, a); + BOOST_TEST_NE(b, a); + axis::category d; + BOOST_TEST_NE(c, d); + d = std::move(c); + BOOST_TEST_EQ(d, a); + BOOST_TEST_EQ(a.size(), 3); + BOOST_TEST_EQ(a(A), 0); + BOOST_TEST_EQ(a(B), 1); + BOOST_TEST_EQ(a(C), 2); + BOOST_TEST_EQ(a(other), 3); + BOOST_TEST_EQ(a.value(0), A); + BOOST_TEST_EQ(a.value(1), B); + BOOST_TEST_EQ(a.value(2), C); + BOOST_TEST_THROWS(a.value(3), std::out_of_range); + } + + // iterators + { + test_axis_iterator(axis::category<>({3, 1, 2}, ""), 0, 3); + test_axis_iterator(axis::category({"A", "B"}, ""), 0, 2); + } + + return boost::report_errors(); +} diff --git a/test/axis_circular_test.cpp b/test/axis_circular_test.cpp new file mode 100644 index 00000000..97a39086 --- /dev/null +++ b/test/axis_circular_test.cpp @@ -0,0 +1,56 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +int main() { + // bad_ctor + { + BOOST_TEST_THROWS(axis::circular<>(0), std::invalid_argument); + BOOST_TEST_THROWS(axis::circular<>(2, 0, -1), std::invalid_argument); + } + + // axis::circular + { + axis::circular<> a{4, 0, 1}; + BOOST_TEST_EQ(a[-1].lower(), a[a.size() - 1].lower() - 1); + axis::circular<> b; + BOOST_TEST_NE(a, b); + b = a; + BOOST_TEST_EQ(a, b); + b = b; + BOOST_TEST_EQ(a, b); + axis::circular<> c = std::move(b); + BOOST_TEST_EQ(c, a); + axis::circular<> d; + BOOST_TEST_NE(c, d); + d = std::move(c); + BOOST_TEST_EQ(d, a); + BOOST_TEST_EQ(a(-1.0 * 3), 0); + BOOST_TEST_EQ(a(0.0), 0); + BOOST_TEST_EQ(a(0.25), 1); + BOOST_TEST_EQ(a(0.5), 2); + BOOST_TEST_EQ(a(0.75), 3); + BOOST_TEST_EQ(a(1.0), 0); + BOOST_TEST_EQ(a(std::numeric_limits::infinity()), 4); + BOOST_TEST_EQ(a(-std::numeric_limits::infinity()), 4); + BOOST_TEST_EQ(a(std::numeric_limits::quiet_NaN()), 4); + } + + // iterators + { + test_axis_iterator(axis::circular<>(5, 0, 1, ""), 0, 5); + } + + return boost::report_errors(); +} diff --git a/test/axis_integer_test.cpp b/test/axis_integer_test.cpp new file mode 100644 index 00000000..b45f73d7 --- /dev/null +++ b/test/axis_integer_test.cpp @@ -0,0 +1,79 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +int main() { + // bad_ctor + { + BOOST_TEST_THROWS(axis::integer<>(1, 1), std::invalid_argument); + BOOST_TEST_THROWS(axis::integer<>(1, -1), std::invalid_argument); + } + + // axis::integer + { + axis::integer<> a{-1, 2}; + BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); + axis::integer<> b; + BOOST_TEST_NE(a, b); + b = a; + BOOST_TEST_EQ(a, b); + b = b; + BOOST_TEST_EQ(a, b); + axis::integer<> c = std::move(b); + BOOST_TEST_EQ(c, a); + axis::integer<> d; + BOOST_TEST_NE(c, d); + d = std::move(c); + BOOST_TEST_EQ(d, a); + BOOST_TEST_EQ(a(-10), -1); + BOOST_TEST_EQ(a(-2), -1); + BOOST_TEST_EQ(a(-1), 0); + BOOST_TEST_EQ(a(0), 1); + BOOST_TEST_EQ(a(1), 2); + BOOST_TEST_EQ(a(2), 3); + BOOST_TEST_EQ(a(10), 3); + } + + // axis::integer with int type + { + axis::integer a{-1, 2}; + BOOST_TEST_EQ(a[-2].value(), std::numeric_limits::min()); + BOOST_TEST_EQ(a[4].value(), std::numeric_limits::max()); + BOOST_TEST_EQ(a(-10), -1); + BOOST_TEST_EQ(a(-2), -1); + BOOST_TEST_EQ(a(-1), 0); + BOOST_TEST_EQ(a(0), 1); + BOOST_TEST_EQ(a(1), 2); + BOOST_TEST_EQ(a(2), 3); + BOOST_TEST_EQ(a(10), 3); + } + + // iterators + { + test_axis_iterator(axis::integer<>(0, 4, ""), 0, 4); + } + + return boost::report_errors(); +} diff --git a/test/axis_regular_test.cpp b/test/axis_regular_test.cpp new file mode 100644 index 00000000..e2392842 --- /dev/null +++ b/test/axis_regular_test.cpp @@ -0,0 +1,158 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +int main() { + // bad_ctors + { + BOOST_TEST_THROWS(axis::regular<>(1, 0, 0), std::invalid_argument); + BOOST_TEST_THROWS(axis::regular<>(0, 0, 1), std::invalid_argument); + } + + // axis::regular + { + axis::regular<> a{4, -2, 2}; + BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); + axis::regular<> b; + BOOST_TEST_NE(a, b); + b = a; + BOOST_TEST_EQ(a, b); + b = b; + BOOST_TEST_EQ(a, b); + axis::regular<> c = std::move(b); + BOOST_TEST_EQ(c, a); + axis::regular<> d; + BOOST_TEST_NE(c, d); + d = std::move(c); + BOOST_TEST_EQ(d, a); + BOOST_TEST_EQ(a(-10.), -1); + BOOST_TEST_EQ(a(-2.1), -1); + BOOST_TEST_EQ(a(-2.0), 0); + BOOST_TEST_EQ(a(-1.1), 0); + BOOST_TEST_EQ(a(0.0), 2); + BOOST_TEST_EQ(a(0.9), 2); + BOOST_TEST_EQ(a(1.0), 3); + BOOST_TEST_EQ(a(10.), 4); + BOOST_TEST_EQ(a(-std::numeric_limits::infinity()), -1); + BOOST_TEST_EQ(a(std::numeric_limits::infinity()), 4); + BOOST_TEST_EQ(a(std::numeric_limits::quiet_NaN()), 4); + } + + // regular axis with inverted range + { + axis::regular<> a{2, 1, -2}; + BOOST_TEST_EQ(a[-1].lower(), std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[0].lower(), 1); + BOOST_TEST_EQ(a[1].lower(), -0.5); + BOOST_TEST_EQ(a[2].lower(), -2); + BOOST_TEST_EQ(a[2].upper(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(a(2), -1); + BOOST_TEST_EQ(a(1.001), -1); + BOOST_TEST_EQ(a(1), 0); + BOOST_TEST_EQ(a(0), 0); + BOOST_TEST_EQ(a(-0.499), 0); + BOOST_TEST_EQ(a(-0.5), 1); + BOOST_TEST_EQ(a(-1), 1); + BOOST_TEST_EQ(a(-2), 2); + BOOST_TEST_EQ(a(-20), 2); + } + + // axis::regular with log transform + { + axis::regular> b{2, 1e0, 1e2}; + BOOST_TEST_EQ(b[-1].lower(), 0.0); + BOOST_TEST_IS_CLOSE(b[0].lower(), 1.0, 1e-9); + BOOST_TEST_IS_CLOSE(b[1].lower(), 10.0, 1e-9); + BOOST_TEST_IS_CLOSE(b[2].lower(), 100.0, 1e-9); + BOOST_TEST_EQ(b[2].upper(), std::numeric_limits::infinity()); + + BOOST_TEST_EQ(b(-1), 2); // produces NaN in conversion + BOOST_TEST_EQ(b(0), -1); + BOOST_TEST_EQ(b(1), 0); + BOOST_TEST_EQ(b(9), 0); + BOOST_TEST_EQ(b(10), 1); + BOOST_TEST_EQ(b(90), 1); + BOOST_TEST_EQ(b(100), 2); + BOOST_TEST_EQ(b(std::numeric_limits::infinity()), 2); + } + + // axis::regular with sqrt transform + { + axis::regular> b{2, 0, 4}; + // this is weird: -inf * -inf = inf, thus the lower bound + BOOST_TEST_EQ(b[-1].lower(), std::numeric_limits::infinity()); + BOOST_TEST_IS_CLOSE(b[0].lower(), 0.0, 1e-9); + BOOST_TEST_IS_CLOSE(b[1].lower(), 1.0, 1e-9); + BOOST_TEST_IS_CLOSE(b[2].lower(), 4.0, 1e-9); + BOOST_TEST_EQ(b[2].upper(), std::numeric_limits::infinity()); + + BOOST_TEST_EQ(b(-1), 2); // produces NaN in conversion + BOOST_TEST_EQ(b(0), 0); + BOOST_TEST_EQ(b(0.99), 0); + BOOST_TEST_EQ(b(1), 1); + BOOST_TEST_EQ(b(3.99), 1); + BOOST_TEST_EQ(b(4), 2); + BOOST_TEST_EQ(b(100), 2); + BOOST_TEST_EQ(b(std::numeric_limits::infinity()), 2); + } + + // axis::regular with quantity + { + using namespace boost::units; + using namespace boost::units::si; + using Q = quantity; + + axis::regular> b(2, 0 * meter, 2 * meter); + BOOST_TEST_EQ(b[-1].lower() / meter, -std::numeric_limits::infinity()); + BOOST_TEST_IS_CLOSE(b[0].lower() / meter, 0.0, 1e-9); + BOOST_TEST_IS_CLOSE(b[1].lower() / meter, 1.0, 1e-9); + BOOST_TEST_IS_CLOSE(b[2].lower() / meter, 2.0, 1e-9); + BOOST_TEST_EQ(b[2].upper() / meter, std::numeric_limits::infinity()); + + BOOST_TEST_EQ(b(-1 * meter), -1); // produces NaN in conversion + BOOST_TEST_EQ(b(0 * meter), 0); + BOOST_TEST_EQ(b(0.99 * meter), 0); + BOOST_TEST_EQ(b(1 * meter), 1); + BOOST_TEST_EQ(b(1.99 * meter), 1); + BOOST_TEST_EQ(b(2 * meter), 2); + BOOST_TEST_EQ(b(100 * meter), 2); + BOOST_TEST_EQ(b(std::numeric_limits::infinity() * meter), 2); + } + + // iterators + { + test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::option_type::none), 0, 5); + test_axis_iterator( + axis::regular<>(5, 0, 1, "", axis::option_type::underflow_and_overflow), 0, 5); + } + + // bin_type streamable + { + auto test = [](const auto& x, const char* ref) { + std::ostringstream os; + os << x; + BOOST_TEST_EQ(os.str(), std::string(ref)); + }; + + auto a = axis::regular<>(2, 0, 1); + test(a[0], "[0, 0.5)"); + } + + return boost::report_errors(); +} diff --git a/test/axis_test.cpp b/test/axis_test.cpp deleted file mode 100644 index e8eda59d..00000000 --- a/test/axis_test.cpp +++ /dev/null @@ -1,496 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "utility.hpp" - -using namespace boost::histogram; - -#define BOOST_TEST_IS_CLOSE(a, b, eps) BOOST_TEST(std::abs(a - b) < eps) - -template -void test_axis_iterator(const Axis& a, int begin, int end) { - for (auto bin : a) { - BOOST_TEST_EQ(bin.idx(), begin); - BOOST_TEST_EQ(bin, a[begin]); - ++begin; - } - BOOST_TEST_EQ(begin, end); - auto rit = a.rbegin(); - for (; rit != a.rend(); ++rit) { - BOOST_TEST_EQ(rit->idx(), --begin); - BOOST_TEST_EQ(*rit, a[begin]); - } -} - -int main() { - // bad_ctors - { - BOOST_TEST_THROWS(axis::regular<>(0, 0, 1), std::logic_error); - BOOST_TEST_THROWS(axis::regular<>(1, 1, -1), std::logic_error); - BOOST_TEST_THROWS(axis::circular<>(0), std::logic_error); - BOOST_TEST_THROWS(axis::variable<>({}), std::logic_error); - BOOST_TEST_THROWS(axis::variable<>({1.0}), std::logic_error); - BOOST_TEST_THROWS(axis::integer<>(1, -1), std::logic_error); - BOOST_TEST_THROWS(axis::category<>({}), std::logic_error); - } - - // axis::regular - { - axis::regular<> a{4, -2, 2}; - BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); - BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); - axis::regular<> b; - BOOST_TEST_NOT(a == b); - b = a; - BOOST_TEST_EQ(a, b); - b = b; - BOOST_TEST_EQ(a, b); - axis::regular<> c = std::move(b); - BOOST_TEST(c == a); - BOOST_TEST_NOT(b == a); - axis::regular<> d; - BOOST_TEST_NOT(c == d); - d = std::move(c); - BOOST_TEST_EQ(d, a); - BOOST_TEST_EQ(a.index(-10.), -1); - BOOST_TEST_EQ(a.index(-2.1), -1); - BOOST_TEST_EQ(a.index(-2.0), 0); - BOOST_TEST_EQ(a.index(-1.1), 0); - BOOST_TEST_EQ(a.index(0.0), 2); - BOOST_TEST_EQ(a.index(0.9), 2); - BOOST_TEST_EQ(a.index(1.0), 3); - BOOST_TEST_EQ(a.index(10.), 4); - BOOST_TEST_EQ(a.index(-std::numeric_limits::infinity()), -1); - BOOST_TEST_EQ(a.index(std::numeric_limits::infinity()), 4); - BOOST_TEST_EQ(a.index(std::numeric_limits::quiet_NaN()), 4); - } - - // axis::regular with log transform - { - axis::regular b{2, 1e0, 1e2}; - BOOST_TEST_EQ(b[-1].lower(), 0.0); - BOOST_TEST_IS_CLOSE(b[0].lower(), 1.0, 1e-9); - BOOST_TEST_IS_CLOSE(b[1].lower(), 10.0, 1e-9); - BOOST_TEST_IS_CLOSE(b[2].lower(), 100.0, 1e-9); - BOOST_TEST_EQ(b[2].upper(), std::numeric_limits::infinity()); - - BOOST_TEST_EQ(b.index(-1), 2); // produces NaN in conversion - BOOST_TEST_EQ(b.index(0), -1); - BOOST_TEST_EQ(b.index(1), 0); - BOOST_TEST_EQ(b.index(9), 0); - BOOST_TEST_EQ(b.index(10), 1); - BOOST_TEST_EQ(b.index(90), 1); - BOOST_TEST_EQ(b.index(100), 2); - BOOST_TEST_EQ(b.index(std::numeric_limits::infinity()), 2); - } - - // axis::regular with sqrt transform - { - axis::regular b{2, 0, 4}; - // this is weird: -inf * -inf = inf, thus the lower bound - BOOST_TEST_EQ(b[-1].lower(), std::numeric_limits::infinity()); - BOOST_TEST_IS_CLOSE(b[0].lower(), 0.0, 1e-9); - BOOST_TEST_IS_CLOSE(b[1].lower(), 1.0, 1e-9); - BOOST_TEST_IS_CLOSE(b[2].lower(), 4.0, 1e-9); - BOOST_TEST_EQ(b[2].upper(), std::numeric_limits::infinity()); - - BOOST_TEST_EQ(b.index(-1), 2); // produces NaN in conversion - BOOST_TEST_EQ(b.index(0), 0); - BOOST_TEST_EQ(b.index(0.99), 0); - BOOST_TEST_EQ(b.index(1), 1); - BOOST_TEST_EQ(b.index(3.99), 1); - BOOST_TEST_EQ(b.index(4), 2); - BOOST_TEST_EQ(b.index(100), 2); - BOOST_TEST_EQ(b.index(std::numeric_limits::infinity()), 2); - } - - // axis::circular - { - axis::circular<> a{4}; - BOOST_TEST_EQ(a[-1].lower(), a[a.size() - 1].lower() - a.perimeter()); - axis::circular<> b; - BOOST_TEST_NOT(a == b); - b = a; - BOOST_TEST_EQ(a, b); - b = b; - BOOST_TEST_EQ(a, b); - axis::circular<> c = std::move(b); - BOOST_TEST(c == a); - BOOST_TEST_NOT(b == a); - axis::circular<> d; - BOOST_TEST_NOT(c == d); - d = std::move(c); - BOOST_TEST_EQ(d, a); - BOOST_TEST_EQ(a.index(-1.0 * a.perimeter()), 0); - BOOST_TEST_EQ(a.index(0.0), 0); - BOOST_TEST_EQ(a.index(0.25 * a.perimeter()), 1); - BOOST_TEST_EQ(a.index(0.5 * a.perimeter()), 2); - BOOST_TEST_EQ(a.index(0.75 * a.perimeter()), 3); - BOOST_TEST_EQ(a.index(1.00 * a.perimeter()), 0); - BOOST_TEST_EQ(a.index(std::numeric_limits::infinity()), 0); - BOOST_TEST_EQ(a.index(-std::numeric_limits::infinity()), 0); - BOOST_TEST_EQ(a.index(std::numeric_limits::quiet_NaN()), 0); - } - - // axis::variable - { - axis::variable<> a{-1, 0, 1}; - BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); - BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); - axis::variable<> b; - BOOST_TEST_NOT(a == b); - b = a; - BOOST_TEST_EQ(a, b); - b = b; - BOOST_TEST_EQ(a, b); - axis::variable<> c = std::move(b); - BOOST_TEST(c == a); - BOOST_TEST_NOT(b == a); - axis::variable<> d; - BOOST_TEST_NOT(c == d); - d = std::move(c); - BOOST_TEST_EQ(d, a); - axis::variable<> e{-2, 0, 2}; - BOOST_TEST_NOT(a == e); - BOOST_TEST_EQ(a.index(-10.), -1); - BOOST_TEST_EQ(a.index(-1.), 0); - BOOST_TEST_EQ(a.index(0.), 1); - BOOST_TEST_EQ(a.index(1.), 2); - BOOST_TEST_EQ(a.index(10.), 2); - BOOST_TEST_EQ(a.index(-std::numeric_limits::infinity()), -1); - BOOST_TEST_EQ(a.index(std::numeric_limits::infinity()), 2); - BOOST_TEST_EQ(a.index(std::numeric_limits::quiet_NaN()), 2); - } - - // axis::integer - { - axis::integer<> a{-1, 2}; - BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::max()); - BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::max()); - axis::integer<> b; - BOOST_TEST_NOT(a == b); - b = a; - BOOST_TEST_EQ(a, b); - b = b; - BOOST_TEST_EQ(a, b); - axis::integer<> c = std::move(b); - BOOST_TEST(c == a); - BOOST_TEST_NOT(b == a); - axis::integer<> d; - BOOST_TEST_NOT(c == d); - d = std::move(c); - BOOST_TEST_EQ(d, a); - BOOST_TEST_EQ(a.index(-10), -1); - BOOST_TEST_EQ(a.index(-2), -1); - BOOST_TEST_EQ(a.index(-1), 0); - BOOST_TEST_EQ(a.index(0), 1); - BOOST_TEST_EQ(a.index(1), 2); - BOOST_TEST_EQ(a.index(2), 3); - BOOST_TEST_EQ(a.index(10), 3); - } - - // axis::category - { - std::string A("A"), B("B"), C("C"), other; - axis::category a{{A, B, C}}; - axis::category b; - BOOST_TEST_NOT(a == b); - b = a; - BOOST_TEST_EQ(a, b); - b = axis::category{{B, A, C}}; - BOOST_TEST_NOT(a == b); - b = a; - b = b; - BOOST_TEST_EQ(a, b); - axis::category c = std::move(b); - BOOST_TEST(c == a); - BOOST_TEST_NOT(b == a); - axis::category d; - BOOST_TEST_NOT(c == d); - d = std::move(c); - BOOST_TEST_EQ(d, a); - BOOST_TEST_EQ(a.size(), 3); - BOOST_TEST_EQ(a.index(A), 0); - BOOST_TEST_EQ(a.index(B), 1); - BOOST_TEST_EQ(a.index(C), 2); - BOOST_TEST_EQ(a.index(other), 3); - BOOST_TEST_EQ(a.value(0), A); - BOOST_TEST_EQ(a.value(1), B); - BOOST_TEST_EQ(a.value(2), C); - BOOST_TEST_THROWS(a.value(3), std::out_of_range); - } - - // iterators - { - enum { A, B, C }; - test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::uoflow_type::off), 0, 5); - test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::uoflow_type::on), 0, 5); - test_axis_iterator(axis::circular<>(5, 0, 1, ""), 0, 5); - test_axis_iterator(axis::variable<>({1, 2, 3}, ""), 0, 2); - test_axis_iterator(axis::integer<>(0, 4, ""), 0, 4); - test_axis_iterator(axis::category<>({A, B, C}, ""), 0, 3); - test_axis_iterator(axis::any_std(axis::regular<>(5, 0, 1)), 0, 5); - BOOST_TEST_THROWS(axis::any_std(axis::category<>({A, B, C})).lower(0), - std::runtime_error); - } - - // axis::any copyable - { - axis::any_std a1(axis::regular<>(2, -1, 1)); - axis::any_std a2(a1); - BOOST_TEST_EQ(a1, a2); - axis::any_std a3; - BOOST_TEST_NE(a3, a1); - a3 = a1; - BOOST_TEST_EQ(a3, a1); - axis::any> a4(axis::regular<>(3, -2, 2)); - axis::any_std a5(a4); - BOOST_TEST_EQ(a4, a5); - axis::any> a6; - a6 = a1; - BOOST_TEST_EQ(a6, a1); - axis::any, axis::integer<>> a7(axis::integer<>(0, 2)); - BOOST_TEST_THROWS(axis::any> a8(a7), std::invalid_argument); - BOOST_TEST_THROWS(a4 = a7, std::invalid_argument); - } - - // axis::any movable - { - axis::any_std a(axis::regular<>(2, -1, 1)); - axis::any_std r(a); - axis::any_std b(std::move(a)); - BOOST_TEST_EQ(b, r); - axis::any_std c; - BOOST_TEST_NOT(a == c); - c = std::move(b); - BOOST_TEST(c == r); - } - - // axis::any streamable - { - enum { A, B, C }; - std::string a = "A"; - std::string b = "B"; - std::vector axes; - axes.push_back(axis::regular<>{2, -1, 1, "regular1"}); - axes.push_back(axis::regular(2, 1, 10, "regular2", - axis::uoflow_type::off)); - axes.push_back(axis::regular(2, 1, 10, "regular3", - axis::uoflow_type::on, 0.5)); - axes.push_back(axis::regular(2, 1, 10, "regular4", - axis::uoflow_type::off, -0.5)); - axes.push_back(axis::circular<>(4, 0.1, 1.0, "polar")); - axes.push_back(axis::variable<>({-1, 0, 1}, "variable", axis::uoflow_type::off)); - axes.push_back(axis::category<>({A, B, C}, "category")); - axes.push_back(axis::category({a, b}, "category2")); - axes.push_back(axis::integer<>(-1, 1, "integer", axis::uoflow_type::off)); - std::ostringstream os; - for (const auto& a : axes) { os << a << "\n"; } - os << axes.back()[0]; - const std::string ref = - "regular(2, -1, 1, label='regular1')\n" - "regular_log(2, 1, 10, label='regular2', uoflow=False)\n" - "regular_pow(2, 1, 10, 0.5, label='regular3')\n" - "regular_pow(2, 1, 10, -0.5, label='regular4', uoflow=False)\n" - "circular(4, phase=0.1, perimeter=1, label='polar')\n" - "variable(-1, 0, 1, label='variable', uoflow=False)\n" - "category(0, 1, 2, label='category')\n" - "category('A', 'B', label='category2')\n" - "integer(-1, 1, label='integer', uoflow=False)\n" - "[-1, 0)"; - BOOST_TEST_EQ(os.str(), ref); - } - - // axis::any equal_comparable - { - enum { A, B, C }; - std::vector axes; - axes.push_back(axis::regular<>{2, -1, 1}); - axes.push_back( - axis::regular(2, 1, 4, "", axis::uoflow_type::on, 0.5)); - axes.push_back(axis::circular<>{4}); - axes.push_back(axis::variable<>{-1, 0, 1}); - axes.push_back(axis::category<>{A, B, C}); - axes.push_back(axis::integer<>{-1, 1}); - for (const auto& a : axes) { - BOOST_TEST(!(a == axis::any_std())); - BOOST_TEST_EQ(a, axis::any_std(a)); - } - BOOST_TEST_NOT(axes == std::vector()); - BOOST_TEST(axes == std::vector(axes)); - } - - // axis::any value_to_index_failure - { - std::string a = "A", b = "B"; - axis::any_std x = axis::category({a, b}, "category"); - BOOST_TEST_THROWS(x.index(1.5), std::runtime_error); - auto cx = static_cast&>(x); - BOOST_TEST_EQ(cx.index(b), 1); - } - - // sequence equality - { - enum { A, B, C }; - std::vector< - axis::any, axis::variable<>, axis::category<>, axis::integer<>>> - std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{A, B, C}}; - - std::vector, axis::variable<>, axis::category<>>> - std_vector2 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B, C}}}; - - std::vector, axis::variable<>>> std_vector3 = { - axis::variable<>{-1, 0, 1}, axis::regular<>{2, -1, 1}}; - - std::vector, axis::regular<>>> std_vector4 = { - axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}}; - - BOOST_TEST(detail::axes_equal(std_vector1, std_vector2)); - BOOST_TEST_NOT(detail::axes_equal(std_vector2, std_vector3)); - BOOST_TEST_NOT(detail::axes_equal(std_vector3, std_vector4)); - - auto tuple1 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B, C}}); - - auto tuple2 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B}}); - - auto tuple3 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}); - - BOOST_TEST(detail::axes_equal(std_vector1, tuple1)); - BOOST_TEST(detail::axes_equal(tuple1, std_vector1)); - BOOST_TEST_NOT(detail::axes_equal(tuple1, tuple2)); - BOOST_TEST_NOT(detail::axes_equal(tuple2, tuple3)); - BOOST_TEST_NOT(detail::axes_equal(std_vector3, tuple3)); - } - - // sequence assign - { - enum { A, B, C, D }; - std::vector< - axis::any, axis::variable<>, axis::category<>, axis::integer<>>> - std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{A, B, C}}; - - std::vector, axis::variable<>, axis::category<>>> - std_vector2 = {axis::regular<>{2, -2, 2}, axis::variable<>{-2, 0, 2}, - axis::category<>{A, B}}; - - detail::axes_assign(std_vector2, std_vector1); - BOOST_TEST(detail::axes_equal(std_vector2, std_vector1)); - - auto tuple1 = std::make_tuple(axis::regular<>{2, -3, 3}, axis::variable<>{-3, 0, 3}, - axis::category<>{A, B, C, D}); - - detail::axes_assign(tuple1, std_vector1); - BOOST_TEST(detail::axes_equal(tuple1, std_vector1)); - - decltype(std_vector1) std_vector3; - BOOST_TEST_NOT(detail::axes_equal(std_vector3, tuple1)); - detail::axes_assign(std_vector3, tuple1); - BOOST_TEST(detail::axes_equal(std_vector3, tuple1)); - - auto tuple2 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{A, B}); - - detail::axes_assign(tuple2, tuple1); - BOOST_TEST(detail::axes_equal(tuple2, tuple1)); - } - - // sub_axes - { - using ra = axis::regular<>; - using ia = axis::integer<>; - using ca = axis::category<>; - using T = std::tuple; - BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - } - - // make_sub_tuple - { - using ia = axis::integer<>; - using T = std::tuple; - auto axes = T(ia(0, 1), ia(1, 2), ia(2, 3)); - BOOST_TEST_EQ(detail::make_sub_axes(axes, i1(), i2()), - (std::tuple(ia(1, 2), ia(2, 3)))); - BOOST_TEST_EQ(detail::make_sub_axes(axes, i0(), i1()), - (std::tuple(ia(0, 1), ia(1, 2)))); - BOOST_TEST_EQ(detail::make_sub_axes(axes, i1()), (std::tuple(ia(1, 2)))); - BOOST_TEST_EQ(detail::make_sub_axes(axes, i0(), i1(), i2()), axes); - } - - // vector of axes with custom allocators - { - using T1 = axis::regular>; - using T2 = axis::circular>; - using T3 = axis::variable>; - using T4 = axis::integer>; - using T5 = axis::category>; - using axis_type = axis::any; // no heap allocation - using axes_type = std::vector>; - - tracing_allocator_db db; - { - auto a = tracing_allocator(db); - const auto label = std::string(512, 'c'); - axes_type axes(a); - axes.reserve(5); - axes.emplace_back(T1(1, 0, 1, label, axis::uoflow_type::on, {}, a)); - axes.emplace_back(T2(2, 0, T2::two_pi(), label, a)); - axes.emplace_back(T3({0., 1., 2.}, label, axis::uoflow_type::on, a)); - axes.emplace_back(T4(0, 4, label, axis::uoflow_type::on, a)); - axes.emplace_back(T5({1, 2, 3, 4, 5}, label, axis::uoflow_type::on, a)); - } - // 5 axis::any objects - BOOST_TEST_EQ(db[typeid(axis_type)].first, db[typeid(axis_type)].second); - BOOST_TEST_EQ(db[typeid(axis_type)].first, 5); - - // 5 labels - BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second); - BOOST_TEST_GE(db[typeid(char)].first, 5 * 512u); - - // nothing to allocate for T1 - // nothing to allocate for T2 - // T3 allocates storage for bin edges - BOOST_TEST_EQ(db[typeid(double)].first, db[typeid(double)].second); - BOOST_TEST_EQ(db[typeid(double)].first, 3u); - // nothing to allocate for T4 - // T5 allocates storage for long array - BOOST_TEST_EQ(db[typeid(long)].first, db[typeid(long)].second); - BOOST_TEST_EQ(db[typeid(long)].first, 5u); - -#if (BOOST_MSVC) - BOOST_TEST_EQ(db.size(), 5); // axis_type, char, double, long + ??? -#else - BOOST_TEST_EQ(db.size(), 4); // axis_type, char, double, long -#endif - } - - return boost::report_errors(); -} diff --git a/test/axis_variable_test.cpp b/test/axis_variable_test.cpp new file mode 100644 index 00000000..719ef1f6 --- /dev/null +++ b/test/axis_variable_test.cpp @@ -0,0 +1,60 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +int main() { + // bad_ctors + { + auto empty = std::vector(0); + BOOST_TEST_THROWS((axis::variable<>(empty)), std::invalid_argument); + BOOST_TEST_THROWS(axis::variable<>({1.0}), std::invalid_argument); + } + + // axis::variable + { + axis::variable<> a{-1, 0, 1}; + BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); + axis::variable<> b; + BOOST_TEST_NE(a, b); + b = a; + BOOST_TEST_EQ(a, b); + b = b; + BOOST_TEST_EQ(a, b); + axis::variable<> c = std::move(b); + BOOST_TEST_EQ(c, a); + BOOST_TEST_NE(b, a); + axis::variable<> d; + BOOST_TEST_NE(c, d); + d = std::move(c); + BOOST_TEST_EQ(d, a); + axis::variable<> e{-2, 0, 2}; + BOOST_TEST_NE(a, e); + BOOST_TEST_EQ(a(-10), -1); + BOOST_TEST_EQ(a(-1), 0); + BOOST_TEST_EQ(a(0), 1); + BOOST_TEST_EQ(a(1), 2); + BOOST_TEST_EQ(a(10), 2); + BOOST_TEST_EQ(a(-std::numeric_limits::infinity()), -1); + BOOST_TEST_EQ(a(std::numeric_limits::infinity()), 2); + BOOST_TEST_EQ(a(std::numeric_limits::quiet_NaN()), 2); + } + + // iterators + { + test_axis_iterator(axis::variable<>({1, 2, 3}, ""), 0, 2); + } + + return boost::report_errors(); +} diff --git a/test/axis_variant_test.cpp b/test/axis_variant_test.cpp new file mode 100644 index 00000000..043f2758 --- /dev/null +++ b/test/axis_variant_test.cpp @@ -0,0 +1,239 @@ +// Copyright 2015-2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +int main() { + { + BOOST_TEST_THROWS(axis::integer<>(1, 1), std::invalid_argument); + } + + { + axis::variant< + axis::integer<>, + axis::category + > a{axis::integer<>(0, 2, "int")}; + BOOST_TEST_EQ(a(-10), -1); + BOOST_TEST_EQ(a(-1), -1); + BOOST_TEST_EQ(a(0), 0); + BOOST_TEST_EQ(a(0.5), 0); + BOOST_TEST_EQ(a(1), 1); + BOOST_TEST_EQ(a(2), 2); + BOOST_TEST_EQ(a(10), 2); + BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); + BOOST_TEST_EQ(a.metadata(), std::string("int")); + BOOST_TEST_EQ(a.options(), axis::option_type::underflow_and_overflow); + + a = axis::category({"A", "B"}, "cat"); + BOOST_TEST_EQ(a("A"), 0); + BOOST_TEST_EQ(a("B"), 1); + BOOST_TEST_EQ(a.metadata(), std::string("cat")); + BOOST_TEST_EQ(a.options(), axis::option_type::overflow); + } + + // axis::variant support for minimal_axis + { + struct minimal_axis { + int operator()(double) const { return 0; } + unsigned size() const { return 1; } + }; + + axis::variant axis; + BOOST_TEST_EQ(axis(0), 0); + BOOST_TEST_EQ(axis(10), 0); + BOOST_TEST_EQ(axis.size(), 1); + BOOST_TEST_THROWS(std::ostringstream() << axis, std::runtime_error); + BOOST_TEST_THROWS(axis.value(0), std::runtime_error); + BOOST_TEST_TRAIT_TRUE( + (std::is_same)); + } + + // axis::variant copyable + { + axis::variant> a1(axis::regular<>(2, -1, 1)); + axis::variant> a2(a1); + BOOST_TEST_EQ(a1, a2); + axis::variant> a3; + BOOST_TEST_NE(a3, a1); + a3 = a1; + BOOST_TEST_EQ(a3, a1); + axis::variant> a4(axis::regular<>(3, -2, 2)); + axis::variant, axis::integer<>> a5(a4); + BOOST_TEST_EQ(a4, a5); + axis::variant> a6; + a6 = a1; + BOOST_TEST_EQ(a6, a1); + axis::variant, axis::integer<>> a7(axis::integer<>(0, 2)); + BOOST_TEST_THROWS(axis::variant> a8(a7), std::runtime_error); + BOOST_TEST_THROWS(a4 = a7, std::runtime_error); + } + + // axis::variant movable + { + axis::variant> a(axis::regular<>(2, -1, 1)); + axis::variant> r(a); + axis::variant> b(std::move(a)); + BOOST_TEST_EQ(b, r); + axis::variant> c; + BOOST_TEST_NOT(a == c); + c = std::move(b); + BOOST_TEST(c == r); + } + + // axis::variant streamable + { + auto test = [](auto&& a, const char* ref) { + using T = detail::rm_cvref; + axis::variant axis(std::move(a)); + std::ostringstream os; + os << axis; + BOOST_TEST_EQ(os.str(), std::string(ref)); + }; + + struct user_defined {}; + + namespace tr = axis::transform; + test(axis::regular<>(2, -1, 1, "regular1"), + "regular(2, -1, 1, metadata=\"regular1\", options=underflow_and_overflow)"); + test(axis::regular>(2, 1, 10, "regular2", axis::option_type::none), + "regular_log(2, 1, 10, metadata=\"regular2\", options=none)"); + test(axis::regular>(1.5, 2, 1, 10, "regular3", axis::option_type::overflow), + "regular_pow(2, 1, 10, metadata=\"regular3\", options=overflow, power=1.5)"); + test(axis::regular>(-1.5, 2, 1, 10, "regular4", axis::option_type::none), + "regular_pow(2, 1, 10, metadata=\"regular4\", options=none, power=-1.5)"); + test(axis::circular(4, 0.1, 1.0), + "circular(4, 0.1, 1.1, options=overflow)"); + test(axis::variable<>({-1, 0, 1}, "variable", axis::option_type::none), + "variable(-1, 0, 1, metadata=\"variable\", options=none)"); + test(axis::category<>({0, 1, 2}, "category"), + "category(0, 1, 2, metadata=\"category\", options=overflow)"); + test(axis::category({"A", "B"}, "category2"), + "category(\"A\", \"B\", metadata=\"category2\", options=overflow)"); +#ifndef _MSC_VER // fails on MSVC because demagnled name for user_defined looks different + test(axis::integer(-1, 1, {}, axis::option_type::none), + "integer(-1, 1, metadata=main::user_defined, options=none)"); +#endif + } + + // bin_type streamable + { + auto test = [](const auto& x, const char* ref) { + std::ostringstream os; + os << x; + BOOST_TEST_EQ(os.str(), std::string(ref)); + }; + + auto b = axis::category<>({1, 2}); + test(b[0], "1"); + } + + // axis::variant equal_comparable + { + enum { A, B, C }; + using variant = axis::variant, axis::regular>, + axis::circular<>, axis::variable<>, axis::category<>, + axis::integer<>>; + std::vector axes; + axes.push_back(axis::regular<>{2, -1, 1}); + axes.push_back(axis::regular>( + 0.5, 2, 1, 4, "", axis::option_type::underflow_and_overflow)); + axes.push_back(axis::circular<>{4}); + axes.push_back(axis::variable<>{-1, 0, 1}); + axes.push_back(axis::category<>({A, B, C})); + axes.push_back(axis::integer<>{-1, 1}); + for (const auto& a : axes) { + BOOST_TEST(!(a == variant())); + BOOST_TEST_EQ(a, variant(a)); + } + BOOST_TEST_NOT(axes == std::vector()); + BOOST_TEST(axes == std::vector(axes)); + } + + // axis::variant with unusual args + { + axis::variant> x = + axis::category({"A", "B"}, "category"); + BOOST_TEST_EQ(x("B"), 1); + } + + { + auto a = axis::variant>(axis::category<>({2, 1, 3})); + BOOST_TEST_THROWS(a[0].lower(), std::runtime_error); + } + + // vector of axes with custom allocators + { + struct null {}; + using M = std::vector>; + using T1 = axis::regular, M>; + using T2 = axis::circular; + using T3 = axis::variable, null>; + using T4 = axis::integer; + using T5 = axis::category, null>; + using axis_type = axis::variant; // no heap allocation + using axes_type = std::vector>; + + tracing_allocator_db db; + { + auto a = tracing_allocator(db); + axes_type axes(a); + axes.reserve(5); + axes.emplace_back(T1(1, 0, 1, M(3, 'c', a))); + axes.emplace_back(T2(2)); + axes.emplace_back( + T3({0., 1., 2.}, {}, axis::option_type::underflow_and_overflow, a)); + axes.emplace_back(T4(0, 4)); + axes.emplace_back(T5({1, 2, 3, 4, 5}, {}, axis::option_type::overflow, a)); + } + // 5 axis::variant objects + BOOST_TEST_EQ(db[typeid(axis_type)].first, db[typeid(axis_type)].second); + BOOST_TEST_EQ(db[typeid(axis_type)].first, 5); + + // label + BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second); + BOOST_TEST_EQ(db[typeid(char)].first, 3u); + + // nothing to allocate for T1 + // nothing to allocate for T2 + // T3 allocates storage for bin edges + BOOST_TEST_EQ(db[typeid(double)].first, db[typeid(double)].second); + BOOST_TEST_EQ(db[typeid(double)].first, 3u); + // nothing to allocate for T4 + // T5 allocates storage for long array + BOOST_TEST_EQ(db[typeid(long)].first, db[typeid(long)].second); + BOOST_TEST_EQ(db[typeid(long)].first, 5u); + +#if (BOOST_MSVC) + BOOST_TEST_EQ(db.size(), 5); // axis_type, char, double, long + ??? +#else + BOOST_TEST_EQ(db.size(), 4); // axis_type, char, double, long +#endif + } + + // iterators + { + test_axis_iterator(axis::variant>(axis::regular<>(5, 0, 1)), 0, 5); + } + + return boost::report_errors(); +} diff --git a/test/detail_test.cpp b/test/detail_test.cpp index dfb90e69..02fa569f 100644 --- a/test/detail_test.cpp +++ b/test/detail_test.cpp @@ -5,39 +5,125 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "utility.hpp" -namespace bhd = boost::histogram::detail; -namespace bhad = boost::histogram::axis::detail; +using namespace boost::histogram; int main() { - // escape0 + BOOST_TEST_EQ(detail::cat("foo", 1, "bar"), "foo1bar"); + + // sequence equality { - std::ostringstream os; - bhad::escape_string(os, std::string("abc")); - BOOST_TEST_EQ(os.str(), std::string("'abc'")); + enum { A, B, C }; + std::vector, axis::variable<>, axis::category<>, + axis::integer<>>> + std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{A, B, C}}; + + std::vector, axis::variable<>, axis::category<>>> + std_vector2 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{{A, B, C}}}; + + std::vector, axis::variable<>>> std_vector3 = { + axis::variable<>{-1, 0, 1}, axis::regular<>{2, -1, 1}}; + + std::vector, axis::regular<>>> std_vector4 = { + axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}}; + + BOOST_TEST(detail::axes_equal(std_vector1, std_vector2)); + BOOST_TEST_NOT(detail::axes_equal(std_vector2, std_vector3)); + BOOST_TEST_NOT(detail::axes_equal(std_vector3, std_vector4)); + + auto tuple1 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{{A, B, C}}); + + auto tuple2 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{{A, B}}); + + auto tuple3 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}); + + BOOST_TEST(detail::axes_equal(std_vector1, tuple1)); + BOOST_TEST(detail::axes_equal(tuple1, std_vector1)); + BOOST_TEST_NOT(detail::axes_equal(tuple1, tuple2)); + BOOST_TEST_NOT(detail::axes_equal(tuple2, tuple3)); + BOOST_TEST_NOT(detail::axes_equal(std_vector3, tuple3)); } - // escape1 + // sequence assign { - std::ostringstream os; - bhad::escape_string(os, std::string("abc\n")); - BOOST_TEST_EQ(os.str(), std::string("'abc\n'")); + enum { A, B, C, D }; + std::vector, axis::variable<>, axis::category<>, + axis::integer<>>> + std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{A, B, C}}; + + std::vector, axis::variable<>, axis::category<>>> + std_vector2 = {axis::regular<>{2, -2, 2}, axis::variable<>{-2, 0, 2}, + axis::category<>{A, B}}; + + detail::axes_assign(std_vector2, std_vector1); + BOOST_TEST(detail::axes_equal(std_vector2, std_vector1)); + + auto tuple1 = std::make_tuple(axis::regular<>{2, -3, 3}, axis::variable<>{-3, 0, 3}, + axis::category<>{A, B, C, D}); + + detail::axes_assign(tuple1, std_vector1); + BOOST_TEST(detail::axes_equal(tuple1, std_vector1)); + + decltype(std_vector1) std_vector3; + BOOST_TEST_NOT(detail::axes_equal(std_vector3, tuple1)); + detail::axes_assign(std_vector3, tuple1); + BOOST_TEST(detail::axes_equal(std_vector3, tuple1)); + + auto tuple2 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{A, B}); + + detail::axes_assign(tuple2, tuple1); + BOOST_TEST(detail::axes_equal(tuple2, tuple1)); } - // escape2 + // sub_axes { - std::ostringstream os; - bhad::escape_string(os, std::string("'abc'")); - BOOST_TEST_EQ(os.str(), std::string("'\\\'abc\\\''")); + using ra = axis::regular<>; + using ia = axis::integer<>; + using ca = axis::category<>; + using T = std::tuple; + BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); } - // cat - { BOOST_TEST_EQ(bhd::cat("foo", 1, "bar"), std::string("foo1bar")); } + // make_sub_tuple + { + using ia = axis::integer<>; + using T = std::tuple; + auto axes = T(ia(0, 1), ia(1, 2), ia(2, 3)); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i1(), i2()), + (std::tuple(ia(1, 2), ia(2, 3)))); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i0(), i1()), + (std::tuple(ia(0, 1), ia(1, 2)))); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i1()), (std::tuple(ia(1, 2)))); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i0(), i1(), i2()), axes); + } return boost::report_errors(); } diff --git a/test/histogram_dynamic_add_fail.cpp b/test/histogram_dynamic_add_fail.cpp deleted file mode 100644 index 741e6647..00000000 --- a/test/histogram_dynamic_add_fail.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#include - -using namespace boost::histogram; -int main() { - auto a = make_dynamic_histogram(axis::integer<>(0, 2)); - auto b = make_dynamic_histogram(axis::integer<>(0, 3)); - a += b; -} diff --git a/test/histogram_dynamic_at_tuple_wrong_dimension_fail.cpp b/test/histogram_dynamic_at_tuple_wrong_dimension_fail.cpp index 0ed96e26..c917276b 100644 --- a/test/histogram_dynamic_at_tuple_wrong_dimension_fail.cpp +++ b/test/histogram_dynamic_at_tuple_wrong_dimension_fail.cpp @@ -6,9 +6,12 @@ #include #include +#include using namespace boost::histogram; int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 2)); + auto v = std::vector>>(); + v.push_back(axis::integer<>(0, 2)); + auto h = make_histogram(v); h.at(std::make_pair(0, 0)); } diff --git a/test/histogram_dynamic_at_vector_wrong_dimension_fail.cpp b/test/histogram_dynamic_at_vector_wrong_dimension_fail.cpp index 9c551867..6e0779ad 100644 --- a/test/histogram_dynamic_at_vector_wrong_dimension_fail.cpp +++ b/test/histogram_dynamic_at_vector_wrong_dimension_fail.cpp @@ -9,6 +9,8 @@ using namespace boost::histogram; int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 2)); + auto v = std::vector>>(); + v.push_back(axis::integer<>(0, 2)); + auto h = make_histogram(v); h.at(std::vector({0, 0})); } diff --git a/test/histogram_dynamic_at_wrong_dimension_fail.cpp b/test/histogram_dynamic_at_wrong_dimension_fail.cpp index 49bd9902..24f3d546 100644 --- a/test/histogram_dynamic_at_wrong_dimension_fail.cpp +++ b/test/histogram_dynamic_at_wrong_dimension_fail.cpp @@ -5,9 +5,12 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include using namespace boost::histogram; int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 2)); + auto v = std::vector>>(); + v.push_back(axis::integer<>(0, 2)); + auto h = make_histogram(v); h.at(0, 0); } diff --git a/test/histogram_dynamic_fill_one_dimensional_tuple_fail.cpp b/test/histogram_dynamic_fill_one_dimensional_tuple_fail.cpp index 177529f3..59d02778 100644 --- a/test/histogram_dynamic_fill_one_dimensional_tuple_fail.cpp +++ b/test/histogram_dynamic_fill_one_dimensional_tuple_fail.cpp @@ -9,6 +9,7 @@ using namespace boost::histogram; int main() { - auto a = make_dynamic_histogram(axis::integer<>(0, 2)); + std::vector>> v(1, axis::integer<>(0, 2)); + auto a = make_histogram(v); a(std::make_tuple(1)); // fails, because tuple is intentionally not unpacked } diff --git a/test/histogram_dynamic_fill_one_dimensional_vector_fail.cpp b/test/histogram_dynamic_fill_one_dimensional_vector_fail.cpp index bb49e7ef..f57d5fe4 100644 --- a/test/histogram_dynamic_fill_one_dimensional_vector_fail.cpp +++ b/test/histogram_dynamic_fill_one_dimensional_vector_fail.cpp @@ -9,6 +9,7 @@ using namespace boost::histogram; int main() { - auto a = make_dynamic_histogram(axis::integer<>(0, 2)); + std::vector>> v(1, axis::integer<>(0, 2)); + auto a = make_histogram(v); a(std::vector(1)); // fails, because tuple is intentionally not unpacked } diff --git a/test/histogram_dynamic_reduce_wrong_order_fail.cpp b/test/histogram_dynamic_reduce_wrong_order_fail.cpp index 201f5ccf..1a180cea 100644 --- a/test/histogram_dynamic_reduce_wrong_order_fail.cpp +++ b/test/histogram_dynamic_reduce_wrong_order_fail.cpp @@ -5,10 +5,14 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include using namespace boost::histogram; int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 1), axis::integer<>(1, 2)); + auto a = std::vector>>(); + a.push_back(axis::integer<>(0, 1)); + a.push_back(axis::integer<>(1, 2)); + auto h = make_histogram(a); auto v = {0, 0}; h.reduce_to(v.begin(), v.end()); } diff --git a/test/histogram_dynamic_test.cpp b/test/histogram_dynamic_test.cpp index ddcbfd8b..e75fd9b6 100644 --- a/test/histogram_dynamic_test.cpp +++ b/test/histogram_dynamic_test.cpp @@ -30,36 +30,35 @@ int main() { // init { - auto v = std::vector, axis::integer<>>>(); + auto v = std::vector, axis::integer<>>>(); v.push_back(axis::regular<>(4, -1, 1)); v.push_back(axis::integer<>(1, 7)); - auto h = make_dynamic_histogram(v.begin(), v.end()); - BOOST_TEST_EQ(h.dim(), 2); + auto h = make_histogram(v.begin(), v.end()); + BOOST_TEST_EQ(h.rank(), 2); BOOST_TEST_EQ(h.axis(0), v[0]); BOOST_TEST_EQ(h.axis(1), v[1]); - auto h2 = make_dynamic_histogram_with(array_storage(), v.begin(), v.end()); - BOOST_TEST_EQ(h2.dim(), 2); + auto h2 = make_histogram_with(array_storage(), v.begin(), v.end()); + BOOST_TEST_EQ(h2.rank(), 2); BOOST_TEST_EQ(h2.axis(0), v[0]); BOOST_TEST_EQ(h2.axis(1), v[1]); } // bad fill argument { - auto h = make_dynamic_histogram(axis::integer<>(0, 3)); + auto h = make(dynamic_tag(), axis::integer<>(0, 3)); BOOST_TEST_THROWS(h(std::string()), std::invalid_argument); } // axis methods { - enum { A, B }; - auto c = make_dynamic_histogram(axis::category<>({A, B})); - BOOST_TEST_THROWS(c.axis().lower(0), std::runtime_error); + auto c = make(dynamic_tag(), axis::category({"A", "B"})); + BOOST_TEST_THROWS(c.axis().value(0), std::runtime_error); } // reduce { - auto h1 = make_dynamic_histogram(axis::integer<>(0, 2), axis::integer<>(0, 3)); + auto h1 = make(dynamic_tag(), axis::integer<>(0, 2), axis::integer<>(0, 3)); h1(0, 0); h1(0, 1); h1(1, 0); @@ -70,7 +69,7 @@ int main() { x = {0}; auto h1_0 = h1.reduce_to(x.begin(), x.end()); - BOOST_TEST_EQ(h1_0.dim(), 1); + BOOST_TEST_EQ(h1_0.rank(), 1); BOOST_TEST_EQ(sum(h1_0), 5); BOOST_TEST_EQ(h1_0.at(0), 2); BOOST_TEST_EQ(h1_0.at(1), 3); @@ -78,7 +77,7 @@ int main() { x = {1}; auto h1_1 = h1.reduce_to(x.begin(), x.end()); - BOOST_TEST_EQ(h1_1.dim(), 1); + BOOST_TEST_EQ(h1_1.rank(), 1); BOOST_TEST_EQ(sum(h1_1), 5); BOOST_TEST_EQ(h1_1.at(0), 2); BOOST_TEST_EQ(h1_1.at(1), 2); @@ -88,13 +87,14 @@ int main() { // histogram iterator { - auto h = make_dynamic_histogram(axis::integer<>(0, 3)); + auto h = + make_s(dynamic_tag(), array_storage>(), axis::integer<>(0, 3)); const auto& a = h.axis(); h(weight(2), 0); h(1); h(1); auto it = h.begin(); - BOOST_TEST_EQ(it.dim(), 1); + BOOST_TEST_EQ(it.rank(), 1); BOOST_TEST_EQ(it.idx(0), 0); BOOST_TEST_EQ(it.bin(0), a[0]); diff --git a/test/histogram_serialization_test.cpp b/test/histogram_serialization_test.cpp index f767cc7c..b25bef24 100644 --- a/test/histogram_serialization_test.cpp +++ b/test/histogram_serialization_test.cpp @@ -4,15 +4,15 @@ // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#include +#include #include #include #include -#include -#include -#include #include -#include +#include #include +#include #include "utility.hpp" using namespace boost::histogram; @@ -21,13 +21,14 @@ template void run_tests() { // histogram_serialization { - enum { A, B, C }; - auto a = make( - Tag(), axis::regular<>(3, -1, 1, "r"), axis::circular<>(4, 0.0, 1.0, "p"), - axis::regular(3, 1, 100, "lr"), - axis::regular(3, 1, 100, "pr", axis::uoflow_type::on, 0.5), - axis::variable<>({0.1, 0.2, 0.3, 0.4, 0.5}, "v"), axis::category<>{A, B, C}, - axis::integer<>(0, 2, "i")); + auto a = make(Tag(), axis::regular<>(3, -1, 1, "axis 0"), + axis::circular<>(4, 0.0, 1.0, "axis 1"), + axis::regular>(3, 1, 100, "axis 2"), + axis::regular>(0.5, 3, 1, 100, "axis 3", + axis::option_type::overflow), + axis::variable<>({0.1, 0.2, 0.3, 0.4, 0.5}, "axis 4"), + axis::category<>{3, 1, 2}, + axis::integer(0, 2)); a(0.5, 0.2, 20, 20, 0.25, 1, 1); std::string buf; { diff --git a/test/histogram_static_add_fail.cpp b/test/histogram_static_add_fail.cpp deleted file mode 100644 index 7e73c286..00000000 --- a/test/histogram_static_add_fail.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#include - -using namespace boost::histogram; -int main() { - auto a = make_static_histogram(axis::integer<>(0, 2)); - auto b = make_static_histogram(axis::integer<>(0, 3)); - a += b; -} diff --git a/test/histogram_static_at_vector_wrong_dimension_fail.cpp b/test/histogram_static_at_vector_wrong_dimension_fail.cpp index c425e536..9cdb7396 100644 --- a/test/histogram_static_at_vector_wrong_dimension_fail.cpp +++ b/test/histogram_static_at_vector_wrong_dimension_fail.cpp @@ -5,9 +5,11 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include using namespace boost::histogram; + int main() { - auto h = make_static_histogram(axis::integer<>(0, 2)); + auto h = make_histogram(axis::integer<>(0, 2)); h.at(std::vector({0, 0})); } diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index 41936d0d..7d961b0f 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -38,60 +39,59 @@ void run_tests() { // init_1 { auto h = make(Tag(), axis::regular<>{3, -1, 1}); - BOOST_TEST_EQ(h.dim(), 1); + BOOST_TEST_EQ(h.rank(), 1); BOOST_TEST_EQ(h.size(), 5); - BOOST_TEST_EQ(h.axis(0_c).shape(), 5); - BOOST_TEST_EQ(h.axis().shape(), 5); + BOOST_TEST_EQ(h.axis(0_c).size(), 3); + BOOST_TEST_EQ(h.axis().size(), 3); auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}); BOOST_TEST_EQ(h2, h); } // init_2 { - auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}); - BOOST_TEST_EQ(h.dim(), 2); - BOOST_TEST_EQ(h.size(), 25); - BOOST_TEST_EQ(h.axis(0_c).shape(), 5); - BOOST_TEST_EQ(h.axis(1_c).shape(), 5); + auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 3}); + BOOST_TEST_EQ(h.rank(), 2); + BOOST_TEST_EQ(h.size(), 30); + BOOST_TEST_EQ(h.axis(0_c).size(), 3); + BOOST_TEST_EQ(h.axis(1_c).size(), 4); auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, - axis::integer<>{-1, 2}); + axis::integer<>{-1, 3}); BOOST_TEST_EQ(h2, h); } // init_3 { auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}); - BOOST_TEST_EQ(h.dim(), 3); - BOOST_TEST_EQ(h.size(), 75); + axis::circular<>{2}); + BOOST_TEST_EQ(h.rank(), 3); + BOOST_TEST_EQ(h.size(), 5 * 5 * 3); auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, - axis::integer<>{-1, 2}, axis::circular<>{3}); + axis::integer<>{-1, 2}, axis::circular<>{2}); BOOST_TEST_EQ(h2, h); } // init_4 { auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}, axis::variable<>{-1, 0, 1}); - BOOST_TEST_EQ(h.dim(), 4); - BOOST_TEST_EQ(h.size(), 300); + axis::circular<>{2}, axis::variable<>{-1, 0, 1}); + BOOST_TEST_EQ(h.rank(), 4); + BOOST_TEST_EQ(h.size(), 5 * 5 * 3 * 4); auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, - axis::integer<>{-1, 2}, axis::circular<>{3}, axis::variable<>{-1, 0, 1}); + axis::integer<>{-1, 2}, axis::circular<>{2}, axis::variable<>{-1, 0, 1}); BOOST_TEST_EQ(h2, h); } // init_5 { - enum { A, B, C }; auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B, C}}); - BOOST_TEST_EQ(h.dim(), 5); - BOOST_TEST_EQ(h.size(), 1200); + axis::circular<>{2}, axis::variable<>{-1, 0, 1}, + axis::category<>{{3, 1, 2}}); + BOOST_TEST_EQ(h.rank(), 5); + BOOST_TEST_EQ(h.size(), 5 * 5 * 3 * 4 * 4); auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, - axis::integer<>{-1, 2}, axis::circular<>{3}, - axis::variable<>{-1, 0, 1}, axis::category<>{{A, B, C}}); + axis::integer<>{-1, 2}, axis::circular<>{2}, + axis::variable<>{-1, 0, 1}, axis::category<>{{3, 1, 2}}); BOOST_TEST_EQ(h2, h); } @@ -131,51 +131,49 @@ void run_tests() { const auto href = h; decltype(h) h2(std::move(h)); // static axes cannot shrink to zero - BOOST_TEST_EQ(h.dim(), expected_moved_from_dim(Tag(), 2)); - BOOST_TEST_EQ(sum(h).value(), 0); + BOOST_TEST_EQ(h.rank(), expected_moved_from_dim(Tag(), 2)); + BOOST_TEST_EQ(sum(h), 0); BOOST_TEST_EQ(h.size(), 0); BOOST_TEST_EQ(h2, href); decltype(h) h3; h3 = std::move(h2); // static axes cannot shrink to zero - BOOST_TEST_EQ(h2.dim(), expected_moved_from_dim(Tag(), 2)); - BOOST_TEST_EQ(sum(h2).value(), 0); + BOOST_TEST_EQ(h2.rank(), expected_moved_from_dim(Tag(), 2)); + BOOST_TEST_EQ(sum(h2), 0); BOOST_TEST_EQ(h2.size(), 0); BOOST_TEST_EQ(h3, href); } // axis methods { - enum { A = 3, B = 5 }; auto a = make(Tag(), axis::regular<>(1, 1, 2, "foo")); BOOST_TEST_EQ(a.axis().size(), 1); - BOOST_TEST_EQ(a.axis().shape(), 3); - BOOST_TEST_EQ(a.axis().index(1), 0); BOOST_TEST_EQ(a.axis()[0].lower(), 1); BOOST_TEST_EQ(a.axis()[0].upper(), 2); - BOOST_TEST_EQ(a.axis().label(), "foo"); - a.axis().label("bar"); - BOOST_TEST_EQ(a.axis().label(), "bar"); + BOOST_TEST_EQ(a.axis().metadata(), "foo"); + a.axis().metadata() = "bar"; + BOOST_TEST_EQ(a.axis().metadata(), "bar"); - auto b = make(Tag(), axis::integer<>(1, 2)); - BOOST_TEST_EQ(b.axis().size(), 1); - BOOST_TEST_EQ(b.axis().shape(), 3); - BOOST_TEST_EQ(b.axis().index(1), 0); - BOOST_TEST_EQ(b.axis()[0].lower(), 1); - BOOST_TEST_EQ(b.axis()[0].upper(), 2); - b.axis().label("foo"); - BOOST_TEST_EQ(b.axis().label(), "foo"); + auto b = make(Tag(), axis::regular<>(1, 1, 2, "foo"), + axis::integer<>(1, 3)); + BOOST_TEST_EQ(b.axis(0_c).size(), 1); + BOOST_TEST_EQ(b.axis(0_c)[0].lower(), 1); + BOOST_TEST_EQ(b.axis(0_c)[0].upper(), 2); + BOOST_TEST_EQ(b.axis(1_c).size(), 2); + BOOST_TEST_EQ(b.axis(1_c)[0].lower(), 1); + BOOST_TEST_EQ(b.axis(1_c)[0].upper(), 2); + b.axis(1_c).metadata() = "bar"; + BOOST_TEST_EQ(b.axis(0_c).metadata(), "foo"); + BOOST_TEST_EQ(b.axis(1_c).metadata(), "bar"); - auto c = make(Tag(), axis::category<>({A, B})); + enum class C { A = 3, B = 5 }; + auto c = make(Tag(), axis::category({C::A, C::B})); BOOST_TEST_EQ(c.axis().size(), 2); - BOOST_TEST_EQ(c.axis().shape(), 3); - BOOST_TEST_EQ(c.axis().index(A), 0); - BOOST_TEST_EQ(c.axis().index(B), 1); - c.axis().label("foo"); - BOOST_TEST_EQ(c.axis().label(), "foo"); + c.axis().metadata() = "foo"; + BOOST_TEST_EQ(c.axis().metadata(), "foo"); // need to cast here for this to work with Tag == dynamic_tag, too - auto ca = static_cast&>(c.axis()); - BOOST_TEST_EQ(ca[0].value(), A); + auto ca = axis::get>(c.axis()); + BOOST_TEST(ca[0].value() == C::A); } // equal_compare @@ -211,9 +209,8 @@ void run_tests() { h(-1); h(10); - BOOST_TEST_EQ(h.dim(), 1); - BOOST_TEST_EQ(h.axis(0_c).size(), 2); - BOOST_TEST_EQ(h.axis(0_c).shape(), 4); + BOOST_TEST_EQ(h.rank(), 1); + BOOST_TEST_EQ(h.axis().size(), 2); BOOST_TEST_EQ(sum(h), 4); BOOST_TEST_EQ(h.at(-1), 1); @@ -224,15 +221,14 @@ void run_tests() { // d1_2 { - auto h = make(Tag(), axis::integer<>(0, 2, "", axis::uoflow_type::off)); + auto h = make(Tag(), axis::integer<>(0, 2, "", axis::option_type::none)); h(0); h(-0); h(-1); h(10); - BOOST_TEST_EQ(h.dim(), 1); - BOOST_TEST_EQ(h.axis(0_c).size(), 2); - BOOST_TEST_EQ(h.axis(0_c).shape(), 2); + BOOST_TEST_EQ(h.rank(), 1); + BOOST_TEST_EQ(h.axis().size(), 2); BOOST_TEST_EQ(sum(h), 2); BOOST_TEST_EQ(h.at(0), 2); @@ -247,18 +243,19 @@ void run_tests() { h("D"); h("E"); - BOOST_TEST_EQ(h.dim(), 1); - BOOST_TEST_EQ(h.axis(0_c).size(), 2); - BOOST_TEST_EQ(h.axis(0_c).shape(), 3); + BOOST_TEST_EQ(h.rank(), 1); + BOOST_TEST_EQ(h.axis().size(), 2); BOOST_TEST_EQ(sum(h), 4); BOOST_TEST_EQ(h.at(0), 1); BOOST_TEST_EQ(h.at(1), 1); + BOOST_TEST_EQ(h.at(2), 2); // overflow bin } // d1w { - auto h = make(Tag(), axis::integer<>(0, 2)); + auto h = make_s(Tag(), array_storage>(), + axis::integer<>(0, 2)); h(-1); h(0); h(weight(0.5), 0); @@ -282,17 +279,15 @@ void run_tests() { // d2 { auto h = make(Tag(), axis::regular<>(2, -1, 1), - axis::integer<>(-1, 2, "", axis::uoflow_type::off)); + axis::integer<>(-1, 2, {}, axis::option_type::none)); h(-1, -1); h(-1, 0); h(-1, -10); h(-10, 0); - BOOST_TEST_EQ(h.dim(), 2); + BOOST_TEST_EQ(h.rank(), 2); BOOST_TEST_EQ(h.axis(0_c).size(), 2); - BOOST_TEST_EQ(h.axis(0_c).shape(), 4); BOOST_TEST_EQ(h.axis(1_c).size(), 3); - BOOST_TEST_EQ(h.axis(1_c).shape(), 3); BOOST_TEST_EQ(sum(h), 3); BOOST_TEST_EQ(h.at(-1, 0), 0); @@ -314,8 +309,10 @@ void run_tests() { // d2w { - auto h = make(Tag(), axis::regular<>(2, -1, 1), - axis::integer<>(-1, 2, "", axis::uoflow_type::off)); + auto h = make_s(Tag(), + array_storage>(), + axis::regular<>(2, -1, 1), + axis::integer<>(-1, 2, {}, axis::option_type::none)); h(-1, 0); // -> 0, 1 h(weight(10), -1, -1); // -> 0, 0 h(weight(5), -1, -10); // is ignored @@ -359,17 +356,20 @@ void run_tests() { // d3w { - auto h = - make(Tag(), axis::integer<>(0, 3), axis::integer<>(0, 4), axis::integer<>(0, 5)); - for (auto i = 0; i < h.axis(0_c).size(); ++i) { - for (auto j = 0; j < h.axis(1_c).size(); ++j) { - for (auto k = 0; k < h.axis(2_c).size(); ++k) { h(weight(i + j + k), i, j, k); } + auto h = make_s(Tag(), + array_storage>(), + axis::integer<>(0, 3), + axis::integer<>(0, 4), + axis::integer<>(0, 5)); + for (auto i = 0u; i < h.axis(0_c).size(); ++i) { + for (auto j = 0u; j < h.axis(1_c).size(); ++j) { + for (auto k = 0u; k < h.axis(2_c).size(); ++k) { h(weight(i + j + k), i, j, k); } } } - for (auto i = 0; i < h.axis(0_c).size(); ++i) { - for (auto j = 0; j < h.axis(1_c).size(); ++j) { - for (auto k = 0; k < h.axis(2_c).size(); ++k) { + for (auto i = 0u; i < h.axis(0_c).size(); ++i) { + for (auto j = 0u; j < h.axis(1_c).size(); ++j) { + for (auto k = 0u; k < h.axis(2_c).size(); ++k) { BOOST_TEST_EQ(h.at(i, j, k).value(), i + j + k); BOOST_TEST_EQ(h.at(i, j, k).variance(), (i + j + k) * (i + j + k)); } @@ -402,8 +402,10 @@ void run_tests() { // add_2 { - auto a = make(Tag(), axis::integer<>(0, 2)); - auto b = make(Tag(), axis::integer<>(0, 2)); + auto a = make_s(Tag(), array_storage>(), + axis::integer<>(0, 2)); + auto b = make_s(Tag(), array_storage>(), + axis::integer<>(0, 2)); a(0); BOOST_TEST_EQ(a.at(0).variance(), 1); @@ -449,7 +451,20 @@ void run_tests() { BOOST_TEST_EQ(d.at(3), 0); } - // functional programming + // bad add + { + auto va = std::vector>>(); + va.push_back(axis::integer<>(0, 2)); + auto a = make_histogram(va); + + auto vb = std::vector>>(); + vb.push_back(axis::integer<>(0, 3)); + auto b = make_histogram(vb); + + BOOST_TEST_THROWS(a += b, std::invalid_argument); + } + + // STL support { auto v = std::vector{0, 1, 2}; auto h = std::for_each(v.begin(), v.end(), make(Tag(), axis::integer<>(0, 3))); @@ -457,6 +472,12 @@ void run_tests() { BOOST_TEST_EQ(h.at(1), 1); BOOST_TEST_EQ(h.at(2), 1); BOOST_TEST_EQ(sum(h), 3); + + auto a = std::vector(); + std::partial_sum(h.begin(), h.end(), std::back_inserter(a)); + BOOST_TEST_EQ(a[0], 1); + BOOST_TEST_EQ(a[1], 2); + BOOST_TEST_EQ(a[2], 3); } // operators @@ -476,20 +497,20 @@ void run_tests() { BOOST_TEST_EQ(d.at(1), 3); auto e = 3 * a; auto f = b * 2; - BOOST_TEST_EQ(e.at(0).value(), 3); - BOOST_TEST_EQ(e.at(1).value(), 0); - BOOST_TEST_EQ(f.at(0).value(), 0); - BOOST_TEST_EQ(f.at(1).value(), 2); + BOOST_TEST_EQ(e.at(0), 3); + BOOST_TEST_EQ(e.at(1), 0); + BOOST_TEST_EQ(f.at(0), 0); + BOOST_TEST_EQ(f.at(1), 2); auto r = a; r += b; r += e; - BOOST_TEST_EQ(r.at(0).value(), 4); - BOOST_TEST_EQ(r.at(1).value(), 1); + BOOST_TEST_EQ(r.at(0), 4); + BOOST_TEST_EQ(r.at(1), 1); BOOST_TEST_EQ(r, a + b + 3 * a); auto s = r / 4; r /= 4; - BOOST_TEST_EQ(r.at(0).value(), 1); - BOOST_TEST_EQ(r.at(1).value(), 0.25); + BOOST_TEST_EQ(r.at(0), 1); + BOOST_TEST_EQ(r.at(1), 0.25); BOOST_TEST_EQ(r, s); } @@ -499,15 +520,17 @@ void run_tests() { std::ostringstream os; os << a; BOOST_TEST_EQ(os.str(), - "histogram(" - "\n regular(3, -1, 1, label='r')," - "\n integer(0, 2, label='i')," - "\n)"); + std::string( + "histogram(\n" + " regular(3, -1, 1, metadata=\"r\", options=underflow_and_overflow),\n" + " integer(0, 2, metadata=\"i\", options=underflow_and_overflow),\n" + ")" + )); } // histogram_reset { - auto h = make(Tag(), axis::integer<>(0, 2, "", axis::uoflow_type::off)); + auto h = make(Tag(), axis::integer<>(0, 2, {}, axis::option_type::none)); h(0); h(1); BOOST_TEST_EQ(h.at(0), 1); @@ -540,16 +563,14 @@ void run_tests() { */ auto h1_0 = h1.reduce_to(0_c); - BOOST_TEST_EQ(h1_0.dim(), 1); + BOOST_TEST_EQ(h1_0.rank(), 1); BOOST_TEST_EQ(sum(h1_0), 5); BOOST_TEST_EQ(h1_0.at(0), 2); BOOST_TEST_EQ(h1_0.at(1), 3); - BOOST_TEST_EQ(h1_0.axis()[0].lower(), 0); - BOOST_TEST_EQ(h1_0.axis()[1].lower(), 1); BOOST_TEST(h1_0.axis() == h1.axis(0_c)); auto h1_1 = h1.reduce_to(1_c); - BOOST_TEST_EQ(h1_1.dim(), 1); + BOOST_TEST_EQ(h1_1.rank(), 1); BOOST_TEST_EQ(sum(h1_1), 5); BOOST_TEST_EQ(h1_1.at(0), 2); BOOST_TEST_EQ(h1_1.at(1), 2); @@ -565,21 +586,21 @@ void run_tests() { h2(1, 0, 2); auto h2_0 = h2.reduce_to(0_c); - BOOST_TEST_EQ(h2_0.dim(), 1); + BOOST_TEST_EQ(h2_0.rank(), 1); BOOST_TEST_EQ(sum(h2_0), 5); BOOST_TEST_EQ(h2_0.at(0), 4); BOOST_TEST_EQ(h2_0.at(1), 1); BOOST_TEST(h2_0.axis() == axis::integer<>(0, 2)); auto h2_1 = h2.reduce_to(1_c); - BOOST_TEST_EQ(h2_1.dim(), 1); + BOOST_TEST_EQ(h2_1.rank(), 1); BOOST_TEST_EQ(sum(h2_1), 5); BOOST_TEST_EQ(h2_1.at(0), 3); BOOST_TEST_EQ(h2_1.at(1), 2); BOOST_TEST(h2_1.axis() == axis::integer<>(0, 3)); auto h2_2 = h2.reduce_to(2_c); - BOOST_TEST_EQ(h2_2.dim(), 1); + BOOST_TEST_EQ(h2_2.rank(), 1); BOOST_TEST_EQ(sum(h2_2), 5); BOOST_TEST_EQ(h2_2.at(0), 2); BOOST_TEST_EQ(h2_2.at(1), 1); @@ -587,7 +608,7 @@ void run_tests() { BOOST_TEST(h2_2.axis() == axis::integer<>(0, 4)); auto h2_01 = h2.reduce_to(0_c, 1_c); - BOOST_TEST_EQ(h2_01.dim(), 2); + BOOST_TEST_EQ(h2_01.rank(), 2); BOOST_TEST_EQ(sum(h2_01), 5); BOOST_TEST_EQ(h2_01.at(0, 0), 2); BOOST_TEST_EQ(h2_01.at(0, 1), 2); @@ -596,7 +617,7 @@ void run_tests() { BOOST_TEST(h2_01.axis(1_c) == axis::integer<>(0, 3)); auto h2_02 = h2.reduce_to(0_c, 2_c); - BOOST_TEST_EQ(h2_02.dim(), 2); + BOOST_TEST_EQ(h2_02.rank(), 2); BOOST_TEST_EQ(sum(h2_02), 5); BOOST_TEST_EQ(h2_02.at(0, 0), 2); BOOST_TEST_EQ(h2_02.at(0, 1), 1); @@ -606,7 +627,7 @@ void run_tests() { BOOST_TEST(h2_02.axis(1_c) == axis::integer<>(0, 4)); auto h2_12 = h2.reduce_to(1_c, 2_c); - BOOST_TEST_EQ(h2_12.dim(), 2); + BOOST_TEST_EQ(h2_12.rank(), 2); BOOST_TEST_EQ(sum(h2_12), 5); BOOST_TEST_EQ(h2_12.at(0, 0), 1); BOOST_TEST_EQ(h2_12.at(1, 0), 1); @@ -619,14 +640,12 @@ void run_tests() { // custom axis { struct custom_axis : public axis::integer<> { - using value_type = const char*; // type that is fed to the axis - using integer::integer; // inherit ctors of base - // the customization point - // - accept const char* and convert to int - // - then call index method of base class - int index(value_type s) const { return integer::index(std::atoi(s)); } + // customization point: convert argument and call base class + int operator()(const char* s) const { + return integer::operator()(std::atoi(s)); + } }; auto h = make(Tag(), custom_axis(0, 3)); @@ -635,7 +654,7 @@ void run_tests() { h("1"); h("9"); - BOOST_TEST_EQ(h.dim(), 1); + BOOST_TEST_EQ(h.rank(), 1); BOOST_TEST_EQ(h.axis(), custom_axis(0, 3)); BOOST_TEST_EQ(h.at(0), 1); BOOST_TEST_EQ(h.at(1), 1); @@ -644,13 +663,14 @@ void run_tests() { // histogram iterator 1D { - auto h = make(Tag(), axis::integer<>(0, 3)); + auto h = make_s(Tag(), array_storage>(), + axis::integer<>(0, 3)); const auto& a = h.axis(); h(weight(2), 0); h(1); h(1); auto it = h.begin(); - BOOST_TEST_EQ(it.dim(), 1); + BOOST_TEST_EQ(it.rank(), 1); BOOST_TEST_EQ(it.idx(), 0); BOOST_TEST_EQ(it.bin(), a[0]); @@ -678,8 +698,10 @@ void run_tests() { // histogram iterator 2D { - auto h = make(Tag(), axis::integer<>(0, 1), - axis::integer<>(2, 4, "", axis::uoflow_type::off)); + auto h = make_s(Tag(), + array_storage>(), + axis::integer<>(0, 1), + axis::integer<>(2, 4, "", axis::option_type::none)); const auto& a0 = h.axis(0_c); const auto& a1 = h.axis(1_c); h(weight(2), 0, 2); @@ -687,7 +709,7 @@ void run_tests() { h(1, 3); auto it = h.begin(); - BOOST_TEST_EQ(it.dim(), 2); + BOOST_TEST_EQ(it.rank(), 2); BOOST_TEST_EQ(it.idx(0), 0); BOOST_TEST_EQ(it.idx(1), 0); @@ -738,20 +760,11 @@ void run_tests() { BOOST_TEST_EQ(v.variance(), 6); } - // STL compatibility - { - auto h = make(Tag(), axis::integer<>(0, 3)); - for (int i = 0; i < 3; ++i) h(i); - auto a = std::vector>(); - std::partial_sum(h.begin(), h.end(), std::back_inserter(a)); - BOOST_TEST_EQ(a[0].value(), 1); - BOOST_TEST_EQ(a[1].value(), 2); - BOOST_TEST_EQ(a[2].value(), 3); - } - // using STL containers { - auto h = make(Tag(), axis::integer<>(0, 2), axis::regular<>(2, 2, 4)); + auto h = make_s(Tag(), + array_storage>(), + axis::integer<>(0, 2), axis::regular<>(2, 2, 4)); // vector in h(std::vector({0, 2})); // pair in @@ -805,24 +818,19 @@ void run_tests() { tracing_allocator_db db; { tracing_allocator a(db); - auto h = make_s(Tag(), array_storage>(a), - axis::integer>( - 0, 1024, std::string(512, 'c'), axis::uoflow_type::on, a)); + auto h = make_s(Tag(), array_storage>(a), + axis::integer<>(0, 1000)); h(0); } // int allocation for array_storage BOOST_TEST_EQ(db[typeid(int)].first, db[typeid(int)].second); - BOOST_TEST_GE(db[typeid(int)].first, 1024u); + BOOST_TEST_EQ(db[typeid(int)].first, 1002u); - // char allocation for axis label - BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second); - BOOST_TEST_GE(db[typeid(char)].first, 512u); - - if (Tag()) { // axis::any allocation, only for dynamic histogram - using T = axis::any>>; + if (Tag()) { // axis::variant allocation, only for dynamic histogram + using T = axis::variant>; BOOST_TEST_EQ(db[typeid(T)].first, db[typeid(T)].second); - BOOST_TEST_GE(db[typeid(T)].first, 1u); + BOOST_TEST_LE(db[typeid(T)].first, 1u); // zero if vector uses small-vector-optimisation } } } diff --git a/test/meta_test.cpp b/test/meta_test.cpp index 41e8abd0..527a0119 100644 --- a/test/meta_test.cpp +++ b/test/meta_test.cpp @@ -6,19 +6,28 @@ #include #include +#include +#include #include #include #include +#include #include #include #include #include #include "utility.hpp" -using namespace boost::histogram::detail; -using namespace boost::histogram::literals; +namespace bh = boost::histogram; +using namespace bh::detail; +using namespace bh::literals; namespace mp11 = boost::mp11; +struct VisitorTestFunctor { + template + T operator()(T&&); +}; + int main() { // literals { @@ -34,67 +43,166 @@ int main() { // has_variance_support { - struct no_methods {}; + struct A {}; - struct value_method { + struct B { void value() {} }; - struct variance_method { + struct C { void variance() {} }; - struct value_and_variance_methods { + struct D { void value() {} void variance() {} }; - BOOST_TEST_TRAIT_FALSE((has_variance_support)); - BOOST_TEST_TRAIT_FALSE((has_variance_support)); - BOOST_TEST_TRAIT_FALSE((has_variance_support)); - BOOST_TEST_TRAIT_TRUE((has_variance_support)); + BOOST_TEST_TRAIT_FALSE((has_variance_support)); + BOOST_TEST_TRAIT_FALSE((has_variance_support)); + BOOST_TEST_TRAIT_FALSE((has_variance_support)); + BOOST_TEST_TRAIT_TRUE((has_variance_support)); } - // has_method_lower + // has_method_value { - struct no_methods {}; - struct lower_method { - void lower(int) {} + struct A {}; + struct B { + void value(int) {} }; - BOOST_TEST_TRAIT_FALSE((has_method_lower)); - BOOST_TEST_TRAIT_TRUE((has_method_lower)); + BOOST_TEST_TRAIT_FALSE((has_method_value)); + BOOST_TEST_TRAIT_TRUE((has_method_value)); + } + + // has_method_options + { + struct NotOptions {}; + struct A {}; + struct B { + NotOptions options(); + }; + struct C { + bh::axis::option_type options(); + }; + + BOOST_TEST_TRAIT_FALSE((has_method_options)); + BOOST_TEST_TRAIT_FALSE((has_method_options)); + BOOST_TEST_TRAIT_TRUE((has_method_options)); + } + + // has_method_metadata + { + struct A {}; + struct B { + void metadata(); + }; + + BOOST_TEST_TRAIT_FALSE((has_method_metadata)); + BOOST_TEST_TRAIT_TRUE((has_method_metadata)); + } + + // is_transform + { + struct A {}; + struct B { + double forward(double); + double inverse(double); + }; + + BOOST_TEST_TRAIT_FALSE((is_transform)); + BOOST_TEST_TRAIT_TRUE((is_transform)); + } + + // is_equal_comparable + { + struct A {}; + struct B { + bool operator==(const B&); + }; + BOOST_TEST_TRAIT_TRUE((is_equal_comparable)); + BOOST_TEST_TRAIT_FALSE((is_equal_comparable)); + BOOST_TEST_TRAIT_TRUE((is_equal_comparable)); + } + + // is_axis + { + struct A {}; + struct B { + int operator()(double); + unsigned size() const; + }; + struct C { + int operator()(double); + }; + struct D { + unsigned size(); + }; + BOOST_TEST_TRAIT_FALSE((is_axis)); + BOOST_TEST_TRAIT_TRUE((is_axis)); + BOOST_TEST_TRAIT_FALSE((is_axis)); + BOOST_TEST_TRAIT_FALSE((is_axis)); + } + + // is_iterable + { + using A = std::vector; + using B = int[3]; + using C = std::initializer_list; + BOOST_TEST_TRAIT_FALSE((is_iterable)); + BOOST_TEST_TRAIT_TRUE((is_iterable)); + BOOST_TEST_TRAIT_TRUE((is_iterable)); + BOOST_TEST_TRAIT_TRUE((is_iterable)); + } + + // is_streamable + { + struct Foo {}; + BOOST_TEST_TRAIT_TRUE((is_streamable)); + BOOST_TEST_TRAIT_TRUE((is_streamable)); + BOOST_TEST_TRAIT_FALSE((is_streamable)); + } + + // is_axis_variant + { + struct A {}; + BOOST_TEST_TRAIT_FALSE((is_axis_variant)); + BOOST_TEST_TRAIT_TRUE((is_axis_variant>)); + BOOST_TEST_TRAIT_TRUE((is_axis_variant>>)); } // classify_container { - using result1 = classify_container; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using A = classify_container; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result1a = classify_container; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using B = classify_container; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result2 = classify_container>; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using C = classify_container>; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result2a = classify_container&>; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using D = classify_container&>; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result3 = classify_container>; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using E = classify_container>; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result3a = classify_container&>; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using F = classify_container&>; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - // (c-)strings are not regarded as dynamic containers - using result4a = classify_container; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using G = classify_container; + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result4b = classify_container; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using H = classify_container; // has no std::end + BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result5 = classify_container; // has no std::end - BOOST_TEST_TRAIT_TRUE((std::is_same)); + using I = classify_container>; + BOOST_TEST_TRAIT_TRUE((std::is_same)); + + auto j = {0, 1}; + using J = classify_container; + BOOST_TEST_TRAIT_TRUE((std::is_same)); } // bool mask @@ -106,7 +214,7 @@ int main() { BOOST_TEST_EQ(v2, std::vector({false, true, false, true})); } - // rm_cv_ref + // rm_cvref { using T1 = int; using T2 = int&&; @@ -116,14 +224,14 @@ int main() { using T6 = volatile int&&; using T7 = volatile const int; using T8 = volatile const int&; - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); } // mp_size @@ -156,5 +264,81 @@ int main() { BOOST_TEST_TRAIT_TRUE((std::is_same)); } + // mp_last + { + using L = mp11::mp_list; + BOOST_TEST_TRAIT_TRUE((std::is_same, long>)); + } + + // container_element_type + { + using T1 = std::vector; + using U1 = container_element_type; + using T2 = const std::vector&; + using U2 = container_element_type; + BOOST_TEST_TRAIT_TRUE((std::is_same)); + BOOST_TEST_TRAIT_TRUE((std::is_same)); + } + + // iterator_value_type + { + using T1 = const char*; + using T2 = std::iterator; + BOOST_TEST_TRAIT_TRUE((std::is_same, char>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + } + + // args_type + { + struct Foo { + static int f1(char); + int f2(long) const; + }; + + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + } + + // visitor_return_type + { + using V1 = bh::axis::variant; + using V2 = bh::axis::variant&; + using V3 = const bh::axis::variant&; + BOOST_TEST_TRAIT_TRUE( + (std::is_same, char>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, int&>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, const long&>)); + } + + // static_if + { + struct callable { + int operator()() { return 1; }; + }; + struct not_callable {}; + auto fcn = [](auto b, auto x) { + return static_if([](auto x) { return x(); }, [](auto) { return 2; }, + x); + }; + BOOST_TEST_EQ(fcn(std::true_type(), callable()), 1); + BOOST_TEST_EQ(fcn(std::false_type(), not_callable()), 2); + } + + // is_axis_vector + { + using A = std::vector>; + using B = std::vector>>; + using C = std::vector; + using D = bh::axis::regular<>; + BOOST_TEST_TRAIT_TRUE((is_axis_vector)); + BOOST_TEST_TRAIT_TRUE((is_axis_vector)); + BOOST_TEST_TRAIT_FALSE((is_axis_vector)); + BOOST_TEST_TRAIT_FALSE((is_axis_vector)); + } + return boost::report_errors(); } diff --git a/test/utility.hpp b/test/utility.hpp index 7acb5a55..668d9e29 100644 --- a/test/utility.hpp +++ b/test/utility.hpp @@ -7,6 +7,7 @@ #ifndef BOOST_HISTOGRAM_TEST_UTILITY_HPP #define BOOST_HISTOGRAM_TEST_UTILITY_HPP +#include #include #include #include @@ -25,7 +26,7 @@ using i2 = boost::mp11::mp_size_t<2>; using i3 = boost::mp11::mp_size_t<3>; namespace std { -// never add to std, we only do it to get ADL working :( +// never add to std, we only do it here to get ADL working :( template ostream& operator<<(ostream& os, const vector& v) { os << "[ "; @@ -34,20 +35,10 @@ ostream& operator<<(ostream& os, const vector& v) { return os; } -namespace detail { - struct ostreamer { - ostream& os; - template - void operator()(const T& t) const { - os << t << " "; - } - }; -} - template ostream& operator<<(ostream& os, const tuple& t) { os << "[ "; - ::boost::mp11::tuple_for_each(t, detail::ostreamer{os}); + ::boost::mp11::tuple_for_each(t, [&os](const auto& x) { os << x << " "; }); os << "]"; return os; } @@ -60,31 +51,38 @@ typename Histogram::element_type sum(const Histogram& h) { return std::accumulate(h.begin(), h.end(), typename Histogram::element_type(0)); } +template +std::vector...>> +make_axis_vector(Ts&& ... ts) { + using T = axis::variant...>; + return std::vector({T(std::forward(ts))...}); +} + using static_tag = std::false_type; using dynamic_tag = std::true_type; template auto make(static_tag, Axes&&... axes) - -> decltype(make_static_histogram(std::forward(axes)...)) { - return make_static_histogram(std::forward(axes)...); + -> decltype(make_histogram(std::forward(axes)...)) { + return make_histogram(std::forward(axes)...); } template auto make_s(static_tag, S&& s, Axes&&... axes) - -> decltype(make_static_histogram_with(s, std::forward(axes)...)) { - return make_static_histogram_with(s, std::forward(axes)...); + -> decltype(make_histogram_with(s, std::forward(axes)...)) { + return make_histogram_with(s, std::forward(axes)...); } template auto make(dynamic_tag, Axes&&... axes) - -> decltype(make_dynamic_histogram...>>(std::forward(axes)...)) { - return make_dynamic_histogram...>>(std::forward(axes)...); + -> decltype(make_histogram(make_axis_vector(std::forward(axes)...))) { + return make_histogram(make_axis_vector(std::forward(axes)...)); } template auto make_s(dynamic_tag, S&& s, Axes&&... axes) - -> decltype(make_dynamic_histogram_with...>>(s, std::forward(axes)...)) { - return make_dynamic_histogram_with...>>(s, std::forward(axes)...); + -> decltype(make_histogram_with(s, make_axis_vector(std::forward(axes)...))) { + return make_histogram_with(s, make_axis_vector(std::forward(axes)...)); } using tracing_allocator_db = std::unordered_map< @@ -132,6 +130,23 @@ constexpr bool operator!=(const tracing_allocator& t, return !operator==(t, u); } +template +void test_axis_iterator(const Axis& a, int begin, int end) { + for (auto bin : a) { + BOOST_TEST_EQ(bin.idx(), begin); + BOOST_TEST_EQ(bin, a[begin]); + ++begin; + } + BOOST_TEST_EQ(begin, end); + auto rit = a.rbegin(); + for (; rit != a.rend(); ++rit) { + BOOST_TEST_EQ(rit->idx(), --begin); + BOOST_TEST_EQ(*rit, a[begin]); + } +} + +#define BOOST_TEST_IS_CLOSE(a, b, eps) BOOST_TEST(std::abs(a - b) < eps) + } // namespace histogram } // namespace boost