From b40a68bb54bf891a32f70dea44bc077ddf123d20 Mon Sep 17 00:00:00 2001 From: Hans Dembinski Date: Thu, 9 Nov 2017 11:38:51 +0100 Subject: [PATCH] improving examples, and fixed bug in Python histogram constructor, now all axis types are recognized --- doc/getting_started.qbk | 151 ++++++++++++++++++------- doc/guide.qbk | 4 +- examples/create_dynamic_histogram.cpp | 51 --------- examples/getting_started_listing_1.cpp | 57 ++++++---- examples/getting_started_listing_2.cpp | 38 ++++--- examples/getting_started_listing_3.py | 19 +++- examples/guide_listing_5.cpp | 1 - examples/guide_listing_7.cpp | 1 - examples/guide_listing_8.cpp | 1 - examples/python_example_1d.py | 19 ---- include/boost/histogram/axis.hpp | 61 +++------- src/python/histogram.cpp | 54 +++++---- 12 files changed, 228 insertions(+), 229 deletions(-) delete mode 100644 examples/create_dynamic_histogram.cpp delete mode 100644 examples/python_example_1d.py diff --git a/doc/getting_started.qbk b/doc/getting_started.qbk index 165abfd6..8bd13cc4 100644 --- a/doc/getting_started.qbk +++ b/doc/getting_started.qbk @@ -1,9 +1,11 @@ [section Getting started] -To get you started, here are some commented usage examples. +To get you started quickly, here are some heavily commented examples to copy paste from. If you prefer a more traditional, structured exposition, check out the [link histogram.guide full user guide]. [section Make and use a static 1d-histogram in C++] +If possible, use the static histogram. It is faster and user errors are caught at compile time. + [c++]`` #include #include @@ -12,13 +14,15 @@ int main(int, char**) { namespace bh = boost::histogram; using namespace bh::literals; // enables _c suffix - // create static 1d-histogram with 10 equidistant bins from -1.0 to 2.0, - // with axis of histogram labeled as "x" + /* + create a static 1d-histogram with an axis that has 10 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<>(10, -1.0, 2.0, "x") ); - // fill histogram with data + // fill histogram with data, typically this would happen in a loop h.fill(-1.5); // put in underflow bin h.fill(-1.0); // included in first bin, bin interval is semi-open h.fill(-0.5); @@ -29,37 +33,50 @@ int main(int, char**) { h.fill(20.0); // put in overflow bin /* - instead of calling h.fill(...) with same argument N times, - use bh::count, which accepts an integer argument N + use bh::count(N) if you would otherwise call h.fill(...) with + *same* argument N times, N is an integer argument */ h.fill(1.0, bh::count(4)); /* - to fill a weighted entry, use bh::weight, which accepts a double - argument; don't confuse with bh::count, it has a different effect - on the variance (see Rationale for a section explaining weighted fills) + do a weighted fill using bh::weight, which accepts a double + - don't mix this with bh::count, both have a different effect on the + variance (see Rationale for an explanation regarding weights) + - if you don't know what this is good for, use bh::count instead, + it is most likeliy what you want and it is more efficient */ h.fill(0.1, bh::weight(2.5)); /* iterate over bins, loop excludes under- and overflow bins - - index 0_c is a compile-time number to make axis(...) return - a different type for each axis - - for-loop yields std::pair<[bin index], [bin type]>, where - [bin type] usually is a semi-open interval representing the bin, - whose edges can be accessed with methods lower() and upper(), but - the [bin type] depends on the axis and could be something else - - value(index) method returns the bin count at index, - - variance(index) method returns a variance estimate of the bin count - at index (see Rationale for a section explaining the variance) + - index 0_c is a compile-time number, the only way in C++ to make + axis(...) to return a different type for each index + - for-loop yields instances of `std::pair`, where + `bin_type` usually is a semi-open interval representing the bin, + whose edges can be accessed with methods `lower()` and `upper()`, + but the [bin type] depends on the axis, look it up in the reference + - `value(index)` method returns the bin count at index + - `variance(index)` method returns a variance estimate of the bin + count at index (see Rationale section for what this means) */ for (const auto& bin : h.axis(0_c)) { - std::cout << "bin " << bin.first - << " x in [" << bin.second.lower() << ", " << bin.second.upper() << "): " - << h.value(bin.first) << " +/- " << std::sqrt(h.variance(bin.first)) + std::cout << "bin " << bin.first << " x in [" + << bin.second.lower() << ", " << bin.second.upper() << "): " + << h.value(bin.first) << " +/- " + << std::sqrt(h.variance(bin.first)) << std::endl; } + // accessing under- and overflow bins is easy, use indices -1 and 10 + std::cout << "underflow bin [" << h.axis(0_c)[-1].lower() + << ", " << h.axis(0_c)[-1].upper() << "): " + << h.value(-1) << " +/- " << std::sqrt(h.variance(-1)) + << std::endl; + std::cout << "overflow bin [" << h.axis(0_c)[10].lower() + << ", " << h.axis(0_c)[10].upper() << "): " + << h.value(10) << " +/- " << std::sqrt(h.variance(10)) + << std::endl; + /* program output: bin 0 x in [-1, -0.7): 1 +/- 1 @@ -72,6 +89,8 @@ int main(int, char**) { bin 7 x in [1.1, 1.4): 1 +/- 1 bin 8 x in [1.4, 1.7): 0 +/- 0 bin 9 x in [1.7, 2): 1 +/- 1 + underflow bin [-inf, -1): 1 +/- 1 + overflow bin [2, inf): 2 +/- 1.41421 */ } @@ -79,57 +98,71 @@ int main(int, char**) { [endsect] -[section Make and use a dynamic 2d-histogram in C++] +[section Make and use a dynamic 3d-histogram in C++] -Here we fill the histogram with some random numbers. +Dynamic histograms are a bit slower than static histograms, but still faster than other libraries. Use a dynamic histogram when you only know at runtime how to layout the histogram, for example, because you wrote a graphical user interface that uses Boost.Histogram underneath. [c++]`` #include #include #include #include +#include namespace br = boost::random; namespace bh = boost::histogram; int main() { /* - create dynamic histogram using `make_dynamic_histogram` - - axis can be passed directly, just like for `make_static_histogram` - - in addition, also accepts iterators over a sequence of axes + 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 */ - std::vector> axes = {bh::axis::regular<>(5, -5, 5, "x"), - bh::axis::regular<>(5, -5, 5, "y")}; + std::vector> axes; + axes.emplace_back(bh::axis::category({"red", "blue"})); + axes.emplace_back(bh::axis::regular<>(5, -5, 5, "x")); + axes.emplace_back(bh::axis::regular<>(5, -5, 5, "y")); auto h = bh::make_dynamic_histogram(axes.begin(), axes.end()); - // fill histogram, random numbers are generated on the fly + // fill histogram with random numbers br::mt19937 gen; br::normal_distribution<> norm; for (int i = 0; i < 1000; ++i) - h.fill(norm(gen), norm(gen)); + h.fill(i % 2 ? "red" : "blue", norm(gen), norm(gen)); /* - print histogram + 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 - - in contrast to the static histogram, we need to cast axis::any - to the held axis type before looping, if the [bin type] is not - convertible to a double interval + - 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 */ - for (const auto& ybin : h.axis(1)) { // rows - for (const auto& xbin : h.axis(0)) { // columns - std::printf("%3.0f ", h.value(xbin.first, ybin.first)); + using cas = bh::axis::category; + for (const auto& cbin : bh::axis::cast(h.axis(0))) { + std::printf("%s\n", cbin.second.c_str()); + for (const auto& ybin : h.axis(2)) { // rows + for (const auto& xbin : h.axis(1)) { // columns + std::printf("%3.0f ", h.value(cbin.first, xbin.first, ybin.first)); + } + std::printf("\n"); } - std::printf("\n"); } } `` +[note +If you care about maximum performance: In this example, `axis::category` is used with two string labels "red" and "blue". It is faster to use an enum, `enum { red, blue };` and a `axis::category<>` axis. +] + +[endsect] [section Make and use a 2d-histogram in Python] You need to build the library with Numpy support to run this example. -[python]`import histogram as hg +[python]`` +import histogram as hg import numpy as np # create 2d-histogram with two axes with 10 equidistant bins from -3 to 3 @@ -153,7 +186,7 @@ y = np.array(h.axis(1)) # creates a view of the counts (no copy involved) count_matrix = np.asarray(h) -# cut off the under- and overflow bins (no copy involved) +# cut off the under- and overflow bins to not confuse matplotib (no copy) reduced_count_matrix = count_matrix[:-2,:-2] try: @@ -164,8 +197,44 @@ try: plt.ylabel(h.axis(1).label) plt.savefig("example_2d_python.png") except ImportError: - # ok, no matplotlib, then just print it + # ok, no matplotlib, then just print the full count matrix print count_matrix + + # output of the print looks something like this, the two right-most rows + # and two down-most columns represent under-/overflow bins + # [[ 0 0 0 1 5 0 0 1 0 0 0 0] + # [ 0 0 0 1 17 11 6 0 0 0 0 0] + # [ 0 0 0 5 31 26 4 1 0 0 0 0] + # [ 0 0 3 20 59 62 26 4 0 0 0 0] + # [ 0 0 1 26 96 89 16 1 0 0 0 0] + # [ 0 0 4 21 86 84 20 1 0 0 0 0] + # [ 0 0 1 24 71 50 15 2 0 0 0 0] + # [ 0 0 0 6 26 37 7 0 0 0 0 0] + # [ 0 0 0 0 11 10 2 0 0 0 0 0] + # [ 0 0 0 1 2 3 1 0 0 0 0 0] + # [ 0 0 0 0 0 2 0 0 0 0 0 0] + # [ 0 0 0 0 0 1 0 0 0 0 0 0]] +`` + +[endsect] + +[section Make and use a 1d-histogram in Python without Numpy] + +Building the library with Numpy support is highly recommended, but just for completeness, here is an example on how to use the library without Numpy support. + +[python]`` +import histogram as hg + +# make 1-d histogram with 10 logarithmic bins from 1e0 to 1e5 +h = hg.histogram(hg.axis.regular_log(10, 1e0, 1e5, "x")) + +# fill histogram with numbers +for x in (1e0, 1e1, 1e2, 1e3, 1e4): + h.fill(x) + +# iterate over bins +for idx, (lower, upper) in enumerate(h.axis(0)): + print "bin %i x in [%g, %g): %g +/- %g" % (idx, lower, upper, h.value(idx), h.variance(idx) ** 0.5) `` [endsect] diff --git a/doc/guide.qbk b/doc/guide.qbk index b2b94605..1577acb7 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -1,4 +1,4 @@ -[section User guide] +[section:guide User guide] How to create and work with histograms is described here. This library is designed to make simple things simple, yet complex things possible. For a quick start, you don't need to read the complete user guide; have a look into the tutorial and the examples instead. This guide covers the basic and more advanced usage of the library. @@ -72,7 +72,6 @@ ranges from `begin` to `end`, where `end` is also not included. When you work with [classref boost::histogram::histogram], you can also create a histogram from a run-time compiled collection of axis objects: [c++]`` -// also see examples/create_dynamic_histogram.cpp #include #include @@ -131,7 +130,6 @@ int main() { Here is a second example which using a weighted fill in a functional programming style. The input values are taken from a container: [c++]`` -// also see examples/create_dynamic_histogram.cpp #include #include #include diff --git a/examples/create_dynamic_histogram.cpp b/examples/create_dynamic_histogram.cpp deleted file mode 100644 index ae42a4ce..00000000 --- a/examples/create_dynamic_histogram.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include -#include - -namespace bh = boost::histogram; - -/* - command line usage: cmd [i a b|r n x y] - a,b,n: integers - x,y : floats -*/ - -int main(int argc, char** argv) { - auto v = std::vector>(); - - // parse arguments - auto argi = 1; - while (argi < argc) { - switch (argv[argi][0]) { - case 'i': { - ++argi; - auto a = std::atoi(argv[argi]); - ++argi; - auto b = std::atoi(argv[argi]); - ++argi; - v.push_back(bh::axis::integer<>(a, b)); - } - break; - case 'r': { - ++argi; - auto n = std::atoi(argv[argi]); - ++argi; - auto x = std::atof(argv[argi]); - ++argi; - auto y = std::atof(argv[argi]); - ++argi; - v.push_back(bh::axis::regular<>(n, x, y)); - } - break; - default: - std::cerr << "unknown argument " << argv[argi] << std::endl; - return 1; - } - } - - auto h = bh::histogram(v.begin(), v.end()); - - // do something with h - std::cout << "you created the following histogram:\n" << h << std::endl; -} diff --git a/examples/getting_started_listing_1.cpp b/examples/getting_started_listing_1.cpp index fb821ba6..45b0320c 100644 --- a/examples/getting_started_listing_1.cpp +++ b/examples/getting_started_listing_1.cpp @@ -5,13 +5,15 @@ int main(int, char**) { namespace bh = boost::histogram; using namespace bh::literals; // enables _c suffix - // create static 1d-histogram with 10 equidistant bins from -1.0 to 2.0, - // with axis of histogram labeled as "x" + /* + create a static 1d-histogram with an axis that has 10 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<>(10, -1.0, 2.0, "x") ); - // fill histogram with data + // fill histogram with data, typically this would happen in a loop h.fill(-1.5); // put in underflow bin h.fill(-1.0); // included in first bin, bin interval is semi-open h.fill(-0.5); @@ -22,37 +24,50 @@ int main(int, char**) { h.fill(20.0); // put in overflow bin /* - instead of calling h.fill(...) with same argument N times, - use bh::count, which accepts an integer argument N + use bh::count(N) if you would otherwise call h.fill(...) with + *same* argument N times, N is an integer argument */ h.fill(1.0, bh::count(4)); /* - to fill a weighted entry, use bh::weight, which accepts a double - argument; don't confuse with bh::count, it has a different effect - on the variance (see Rationale for a section explaining weighted fills) + do a weighted fill using bh::weight, which accepts a double + - don't mix this with bh::count, both have a different effect on the + variance (see Rationale for an explanation regarding weights) + - if you don't know what this is good for, use bh::count instead, + it is most likeliy what you want and it is more efficient */ h.fill(0.1, bh::weight(2.5)); /* iterate over bins, loop excludes under- and overflow bins - - index 0_c is a compile-time number to make axis(...) return - a different type for each axis - - for-loop yields std::pair<[bin index], [bin type]>, where - [bin type] usually is a semi-open interval representing the bin, - whose edges can be accessed with methods lower() and upper(), but - the [bin type] depends on the axis and could be something else - - value(index) method returns the bin count at index, - - variance(index) method returns a variance estimate of the bin count - at index (see Rationale for a section explaining the variance) + - index 0_c is a compile-time number, the only way in C++ to make + axis(...) to return a different type for each index + - for-loop yields instances of `std::pair`, where + `bin_type` usually is a semi-open interval representing the bin, + whose edges can be accessed with methods `lower()` and `upper()`, + but the [bin type] depends on the axis, look it up in the reference + - `value(index)` method returns the bin count at index + - `variance(index)` method returns a variance estimate of the bin + count at index (see Rationale section for what this means) */ for (const auto& bin : h.axis(0_c)) { - std::cout << "bin " << bin.first - << " x in [" << bin.second.lower() << ", " << bin.second.upper() << "): " - << h.value(bin.first) << " +/- " << std::sqrt(h.variance(bin.first)) + std::cout << "bin " << bin.first << " x in [" + << bin.second.lower() << ", " << bin.second.upper() << "): " + << h.value(bin.first) << " +/- " + << std::sqrt(h.variance(bin.first)) << std::endl; } + // accessing under- and overflow bins is easy, use indices -1 and 10 + std::cout << "underflow bin [" << h.axis(0_c)[-1].lower() + << ", " << h.axis(0_c)[-1].upper() << "): " + << h.value(-1) << " +/- " << std::sqrt(h.variance(-1)) + << std::endl; + std::cout << "overflow bin [" << h.axis(0_c)[10].lower() + << ", " << h.axis(0_c)[10].upper() << "): " + << h.value(10) << " +/- " << std::sqrt(h.variance(10)) + << std::endl; + /* program output: bin 0 x in [-1, -0.7): 1 +/- 1 @@ -65,6 +80,8 @@ int main(int, char**) { bin 7 x in [1.1, 1.4): 1 +/- 1 bin 8 x in [1.4, 1.7): 0 +/- 0 bin 9 x in [1.7, 2): 1 +/- 1 + underflow bin [-inf, -1): 1 +/- 1 + overflow bin [2, inf): 2 +/- 1.41421 */ } diff --git a/examples/getting_started_listing_2.cpp b/examples/getting_started_listing_2.cpp index 6e6bd542..4793694f 100644 --- a/examples/getting_started_listing_2.cpp +++ b/examples/getting_started_listing_2.cpp @@ -2,38 +2,46 @@ #include #include #include +#include namespace br = boost::random; namespace bh = boost::histogram; int main() { /* - create dynamic histogram using `make_dynamic_histogram` - - axis can be passed directly, just like for `make_static_histogram` - - in addition, also accepts iterators over a sequence of axes + 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 */ - std::vector> axes = {bh::axis::regular<>(5, -5, 5, "x"), - bh::axis::regular<>(5, -5, 5, "y")}; + std::vector> axes; + axes.emplace_back(bh::axis::category({"red", "blue"})); + axes.emplace_back(bh::axis::regular<>(5, -5, 5, "x")); + axes.emplace_back(bh::axis::regular<>(5, -5, 5, "y")); auto h = bh::make_dynamic_histogram(axes.begin(), axes.end()); - // fill histogram, random numbers are generated on the fly + // fill histogram with random numbers br::mt19937 gen; br::normal_distribution<> norm; for (int i = 0; i < 1000; ++i) - h.fill(norm(gen), norm(gen)); + h.fill(i % 2 ? "red" : "blue", norm(gen), norm(gen)); /* - print histogram + 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 - - in contrast to the static histogram, we need to cast axis::any - to the held axis type before looping, if the [bin type] is not - convertible to a double interval + - 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 */ - for (const auto& ybin : h.axis(1)) { // rows - for (const auto& xbin : h.axis(0)) { // columns - std::printf("%3.0f ", h.value(xbin.first, ybin.first)); + using cas = bh::axis::category; + for (const auto& cbin : bh::axis::cast(h.axis(0))) { + std::printf("%s\n", cbin.second.c_str()); + for (const auto& ybin : h.axis(2)) { // rows + for (const auto& xbin : h.axis(1)) { // columns + std::printf("%3.0f ", h.value(cbin.first, xbin.first, ybin.first)); + } + std::printf("\n"); } - std::printf("\n"); } } diff --git a/examples/getting_started_listing_3.py b/examples/getting_started_listing_3.py index 4d26e5b8..2d5b4b03 100644 --- a/examples/getting_started_listing_3.py +++ b/examples/getting_started_listing_3.py @@ -22,7 +22,7 @@ y = np.array(h.axis(1)) # creates a view of the counts (no copy involved) count_matrix = np.asarray(h) -# cut off the under- and overflow bins (no copy involved) +# cut off the under- and overflow bins to not confuse matplotib (no copy) reduced_count_matrix = count_matrix[:-2,:-2] try: @@ -33,5 +33,20 @@ try: plt.ylabel(h.axis(1).label) plt.savefig("example_2d_python.png") except ImportError: - # ok, no matplotlib, then just print it + # ok, no matplotlib, then just print the full count matrix print count_matrix + + # output of the print looks something like this, the two right-most rows + # and two down-most columns represent under-/overflow bins + # [[ 0 0 0 1 5 0 0 1 0 0 0 0] + # [ 0 0 0 1 17 11 6 0 0 0 0 0] + # [ 0 0 0 5 31 26 4 1 0 0 0 0] + # [ 0 0 3 20 59 62 26 4 0 0 0 0] + # [ 0 0 1 26 96 89 16 1 0 0 0 0] + # [ 0 0 4 21 86 84 20 1 0 0 0 0] + # [ 0 0 1 24 71 50 15 2 0 0 0 0] + # [ 0 0 0 6 26 37 7 0 0 0 0 0] + # [ 0 0 0 0 11 10 2 0 0 0 0 0] + # [ 0 0 0 1 2 3 1 0 0 0 0 0] + # [ 0 0 0 0 0 2 0 0 0 0 0 0] + # [ 0 0 0 0 0 1 0 0 0 0 0 0]] diff --git a/examples/guide_listing_5.cpp b/examples/guide_listing_5.cpp index 8f8d11d0..f7a04292 100644 --- a/examples/guide_listing_5.cpp +++ b/examples/guide_listing_5.cpp @@ -1,4 +1,3 @@ -// also see examples/create_dynamic_histogram.cpp #include #include diff --git a/examples/guide_listing_7.cpp b/examples/guide_listing_7.cpp index 2de36880..4afd4667 100644 --- a/examples/guide_listing_7.cpp +++ b/examples/guide_listing_7.cpp @@ -1,4 +1,3 @@ -// also see examples/create_dynamic_histogram.cpp #include #include #include diff --git a/examples/guide_listing_8.cpp b/examples/guide_listing_8.cpp index 2de36880..4afd4667 100644 --- a/examples/guide_listing_8.cpp +++ b/examples/guide_listing_8.cpp @@ -1,4 +1,3 @@ -// also see examples/create_dynamic_histogram.cpp #include #include #include diff --git a/examples/python_example_1d.py b/examples/python_example_1d.py deleted file mode 100644 index 345497bd..00000000 --- a/examples/python_example_1d.py +++ /dev/null @@ -1,19 +0,0 @@ -import histogram as hg -import numpy as np - -h = hg.histogram(hg.axis.regular(10, -3, 3, "x")) -h.fill(np.random.randn(1000)) - -x = np.array(h.axis(0)) # axis instances behave like sequences -y = np.asarray(h) # creates a view (no copy involved) -y = y[:len(h.axis(0))] # cut off underflow/overflow bins; y[:-2] also works -y = np.append(y, [0]) # extra zero needed by matplotlib's plot(...) function - -try: - import matplotlib.pyplot as plt - plt.plot(x, y, drawstyle="steps-post") - plt.xlabel(h.axis(0).label) - plt.ylabel("counts") - plt.savefig("example_1d_python.png") -except ImportError: - pass \ No newline at end of file diff --git a/include/boost/histogram/axis.hpp b/include/boost/histogram/axis.hpp index 7d4927a0..72241569 100644 --- a/include/boost/histogram/axis.hpp +++ b/include/boost/histogram/axis.hpp @@ -41,66 +41,33 @@ namespace axis { enum class uoflow { off = false, on = true }; -namespace detail { -// similar to boost::reference_wrapper, but with default ctor -template class cref { -public: - cref() = default; - cref(const cref &o) : ptr_(o.ptr_) {} - cref &operator=(const cref &o) { - ptr_ = o.ptr_; - return *this; - } - cref(const T &t) : ptr_(&t) {} - cref &operator=(const T &t) { - ptr_ = &t; - return *this; - } - operator const T &() const { return *ptr_; } - -private: - const T *ptr_ = nullptr; -}; - -template -using axis_iterator_value_t = std::pair< - int, typename conditional< - is_reference::value, - cref::type>, - typename Axis::bin_type>::type>; -} // namespace detail - template class axis_iterator : public iterator_facade, - detail::axis_iterator_value_t, - random_access_traversal_tag> { + std::pair, + random_access_traversal_tag, + std::pair> { public: - using value_type = detail::axis_iterator_value_t; - - explicit axis_iterator(const Axis &axis, int idx) : axis_(axis) { - value_.first = idx; - } + explicit axis_iterator(const Axis &axis, int idx) : axis_(axis), idx_(idx) {} axis_iterator(const axis_iterator &o) = default; axis_iterator &operator=(const axis_iterator &o) = default; private: - void increment() noexcept { ++value_.first; } - void decrement() noexcept { --value_.first; } - void advance(int n) noexcept { value_.first += n; } + void increment() noexcept { ++idx_; } + void decrement() noexcept { --idx_; } + void advance(int n) noexcept { idx_ += n; } int distance_to(const axis_iterator &other) const noexcept { - return other.value_.first - value_.first; + return other.idx_ - idx_; } bool equal(const axis_iterator &other) const noexcept { - return value_.first == other.value_.first; + return idx_ == other.idx_; } - value_type &dereference() const { - value_.second = axis_[value_.first]; - return value_; + std::pair dereference() const { + return std::make_pair(idx_, axis_[idx_]); } - const Axis &axis_; - mutable value_type value_; + const Axis& axis_; + int idx_; friend class boost::iterator_core_access; }; @@ -552,7 +519,7 @@ template class category : public axis_base { public: using value_type = T; - using bin_type = const value_type &; + using bin_type = T; using const_iterator = axis_iterator>; category() = default; diff --git a/src/python/histogram.cpp b/src/python/histogram.cpp index a3e7d0a1..5558002b 100644 --- a/src/python/histogram.cpp +++ b/src/python/histogram.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef HAVE_NUMPY #include namespace np = boost::python::numpy; @@ -121,6 +122,22 @@ struct axis_visitor : public static_visitor { } }; +struct axes_appender { + python::object obj; + std::vector& axes; + bool& success; + axes_appender(python::object o, std::vector& a, + bool& s) : obj(o), axes(a), success(s) {} + template void operator()(const A&) const { + if (success) return; + python::extract get_axis(obj); + if (get_axis.check()) { + axes.emplace_back(get_axis()); + success = true; + } + } +}; + python::object histogram_axis(const dynamic_histogram &self, int i) { if (i < 0) i += self.dim(); @@ -147,35 +164,16 @@ python::object histogram_init(python::tuple args, python::dict kwargs) { std::vector axes; for (unsigned i = 0; i < dim; ++i) { python::object pa = args[i + 1]; - python::extract> er(pa); - if (er.check()) { - axes.push_back(er()); - continue; + bool success = false; + mpl::for_each( + axes_appender(pa, axes, success) + ); + if (!success) { + std::string msg = "require an axis object, got "; + msg += python::extract(pa.attr("__class__"))(); + PyErr_SetString(PyExc_TypeError, msg.c_str()); + python::throw_error_already_set(); } - python::extract> ep(pa); - if (ep.check()) { - axes.push_back(ep()); - continue; - } - python::extract> ev(pa); - if (ev.check()) { - axes.push_back(ev()); - continue; - } - python::extract> ei(pa); - if (ei.check()) { - axes.push_back(ei()); - continue; - } - python::extract> ec(pa); - if (ec.check()) { - axes.push_back(ec()); - continue; - } - std::string msg = "require an axis object, got "; - msg += python::extract(pa.attr("__class__").attr("__name__"))(); - PyErr_SetString(PyExc_TypeError, msg.c_str()); - python::throw_error_already_set(); } dynamic_histogram h(axes.begin(), axes.end()); return pyinit(h);