From 757d4b0ca45b413f95a1a6def76def6649f57585 Mon Sep 17 00:00:00 2001 From: Hans Dembinski Date: Thu, 2 Nov 2017 21:43:04 +0100 Subject: [PATCH] interface break for axis, new category axis --- build/CMakeLists.txt | 21 +- examples/example_1d.cpp | 6 +- examples/example_2d.cpp | 2 +- examples/python_example_1d.py | 2 +- examples/python_fill_cpp.py | 4 +- include/boost/histogram/axis.hpp | 297 +++++++++--------- .../histogram/axis_ostream_operators.hpp | 33 +- .../boost/histogram/detail/axis_visitor.hpp | 50 ++- include/boost/histogram/detail/utility.hpp | 11 +- include/boost/histogram/serialization.hpp | 26 +- include/boost/histogram/utility.hpp | 31 +- src/python/axis.cpp | 157 ++++++--- src/python/histogram.cpp | 6 +- src/python/serialization_suite.hpp | 4 +- test/axis_test.cpp | 119 +++---- test/histogram_test.cpp | 160 +++++----- test/python_suite_test.py | 199 ++++++------ 17 files changed, 610 insertions(+), 518 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 3ef15460..332a6523 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -23,8 +23,18 @@ else() endif() if(BUILD_PYTHON) - find_package(Boost 1.55 REQUIRED - COMPONENTS iostreams serialization python) + if(BUILD_NUMPY_SUPPORT) + find_package(Boost 1.55 REQUIRED + COMPONENTS iostreams serialization python numpy) + find_package(Numpy 1.7 REQUIRED) + set(HAVE_NUMPY 1) + include_directories(${NUMPY_INCLUDE_DIR}) + add_definitions(-DHAVE_NUMPY) + else() + find_package(Boost 1.55 REQUIRED + COMPONENTS iostreams serialization python) + endif() + if(DEFINED PYTHON_VERSION) find_package(PythonLibs ${PYTHON_VERSION} EXACT REQUIRED) find_package(PythonInterp ${PYTHON_VERSION} EXACT REQUIRED) # used by python_suite_test and FindNumpy @@ -38,13 +48,6 @@ if(BUILD_PYTHON) set(HAVE_PYTHON 1) include_directories(${PYTHON_INCLUDE_DIRS}) - if(BUILD_NUMPY_SUPPORT) - find_package(Numpy 1.7 REQUIRED) - set(HAVE_NUMPY 1) - include_directories(${NUMPY_INCLUDE_DIR}) - add_definitions(-DHAVE_NUMPY) - endif() - add_library(histogram SHARED ../src/python/module.cpp ../src/python/axis.cpp diff --git a/examples/example_1d.cpp b/examples/example_1d.cpp index 0d7512b5..b2bdfc66 100644 --- a/examples/example_1d.cpp +++ b/examples/example_1d.cpp @@ -29,9 +29,9 @@ int main(int, char**) { // iterate over bins, loop includes under- and overflow bin for (const auto& bin : h.axis(0_c)) { - std::cout << "bin " << bin.idx - << " x in [" << bin.left << ", " << bin.right << "): " - << h.value(bin.idx) << " +/- " << std::sqrt(h.variance(bin.idx)) + 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; } diff --git a/examples/example_2d.cpp b/examples/example_2d.cpp index 2ae5af40..dbc8dc34 100644 --- a/examples/example_2d.cpp +++ b/examples/example_2d.cpp @@ -28,7 +28,7 @@ int main() { // show histogram for (const auto& ybin : h.axis(1_c)) { // vertical for (const auto& xbin : h.axis(0_c)) { // horizontal - std::printf("%3.0f ", h.value(xbin.idx, ybin.idx)); + std::printf("%3.0f ", h.value(xbin.first, ybin.first)); } std::printf("\n"); } diff --git a/examples/python_example_1d.py b/examples/python_example_1d.py index 8562f3a1..345497bd 100644 --- a/examples/python_example_1d.py +++ b/examples/python_example_1d.py @@ -6,7 +6,7 @@ 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[:h.axis(0).bins] # cut off underflow/overflow bins; y[:-2] also works +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: diff --git a/examples/python_fill_cpp.py b/examples/python_fill_cpp.py index 21a3bb3d..f2c38e54 100644 --- a/examples/python_fill_cpp.py +++ b/examples/python_fill_cpp.py @@ -6,7 +6,7 @@ h = bh.histogram(bh.axis.regular(5, -5, 5, "x"), cpp_filler.process(h) # histogram is filled with input values in c++ -for iy in range(h.axis(1).bins): - for ix in range(h.axis(0).bins): +for iy in range(len(h.axis(1))): + for ix in range(len(h.axis(0))): print "%3i" % h.value(ix, iy), print \ No newline at end of file diff --git a/include/boost/histogram/axis.hpp b/include/boost/histogram/axis.hpp index d9860856..d77bb284 100644 --- a/include/boost/histogram/axis.hpp +++ b/include/boost/histogram/axis.hpp @@ -7,20 +7,14 @@ #ifndef _BOOST_HISTOGRAM_AXIS_HPP_ #define _BOOST_HISTOGRAM_AXIS_HPP_ +#include #include #include #include #include #include -#include -#if BOOST_VERSION < 106100 -#include -namespace boost { -using string_view = string_ref; -} -#else +#include #include -#endif #include #include #include @@ -28,69 +22,74 @@ using string_view = string_ref; #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 { enum { with_uoflow = true, without_uoflow = false }; -template struct bin { - int idx; - Value value; -}; +namespace detail { + // similar to boost::reference_wrapper, but with default ctor + template + class cref { + public: + cref() = default; + cref(const T& t) : ptr_(&t) {} + operator const T&() const { return *ptr_; } + private: + const T* ptr_ = nullptr; + }; -template struct real_bin { - int idx; - Value left, right; -}; - -template -using axis_bin = typename std::conditional::value, - real_bin, bin>::type; + template + using axis_iterator_value = + std::pair::value, + cref< + typename remove_reference::type + >, + typename Axis::bin_type + >::type + >; +} template class axis_iterator : public iterator_facade, - const axis_bin, + detail::axis_iterator_value, random_access_traversal_tag> { - using bin_type = axis_bin; - + using value_type = detail::axis_iterator_value; public: - explicit axis_iterator(const Axis &axis, int idx) : axis_(axis), value_() { - value_.idx = idx; - } + explicit axis_iterator(const Axis &axis, int idx) + : axis_(axis) { value_.first = idx; } private: - void increment() { ++value_.idx; } - void decrement() { --value_.idx; } - void advance(int n) { value_.idx += n; } - int distance_to(const axis_iterator &other) const { - return other.value_.idx - value_.idx; + void increment() noexcept { ++value_.first; } + void decrement() noexcept { --value_.first; } + void advance(int n) noexcept { value_.first += n; } + int distance_to(const axis_iterator &other) const noexcept { + return other.value_.first - value_.first; } - bool equal(const axis_iterator &other) const { - return value_.idx == other.value_.idx; + bool equal(const axis_iterator &other) const noexcept { + return value_.first == other.value_.first; } - const bin_type &dereference() const { - assign_impl(value_); + value_type& dereference() const { + value_.second = axis_[value_.first]; return value_; } - template void assign_impl(bin &v) const { - v.value = axis_[v.idx]; - } - template void assign_impl(real_bin &v) const { - v.left = axis_[v.idx]; - v.right = axis_[v.idx + 1]; - } const Axis &axis_; - mutable bin_type value_; + mutable value_type value_; friend class boost::iterator_core_access; }; @@ -101,7 +100,7 @@ template class axis_base; template <> class axis_base { public: /// Returns the number of bins, excluding overflow/underflow. - inline int bins() const { return size_; } + inline int size() const { return size_; } /// Returns the number of bins, including overflow/underflow. inline int shape() const { return shape_; } /// Returns whether axis has extra overflow and underflow bins. @@ -157,7 +156,7 @@ private: template <> class axis_base { public: /// Returns the number of bins, excluding overflow/underflow. - inline int bins() const { return size_; } + inline int size() const { return size_; } /// Returns the number of bins, including overflow/underflow. inline int shape() const { return size_; } /// Returns whether axis has extra overflow and underflow bins. @@ -230,8 +229,10 @@ template class Transform = transform::identity> class regular : public axis_base, boost::operators> { + using transform = Transform; public: using value_type = RealType; + using bin_type = interval; using const_iterator = axis_iterator; /** Construct axis with n bins over range [min, max). @@ -245,8 +246,8 @@ public: regular(unsigned n, value_type min, value_type max, string_view label = string_view(), bool uoflow = true) : axis_base(n, label, uoflow), - min_(Transform::forward(min)), - delta_((Transform::forward(max) - min_) / n) { + min_(transform::forward(min)), + delta_((transform::forward(max) - min_) / n) { if (!(min < max)) { throw std::logic_error("min < max required"); } @@ -261,23 +262,23 @@ public: /// Returns the bin index for the passed argument. inline int index(value_type x) const noexcept { // Optimized code - const value_type z = (Transform::forward(x) - min_) / delta_; - return z >= 0.0 ? (z > bins() ? bins() : static_cast(z)) : -1; + const value_type z = (transform::forward(x) - min_) / delta_; + return z >= 0.0 ? (z > size() ? size() : static_cast(z)) : -1; } /// Returns the starting edge of the bin. - value_type operator[](int idx) const { - if (idx < 0) { - return Transform::inverse( - -std::numeric_limits::infinity()); - } - if (idx > bins()) { - return Transform::inverse( - std::numeric_limits::infinity()); - } - const value_type z = value_type(idx) / bins(); - return Transform::inverse((1.0 - z) * min_ + - z * (min_ + delta_ * bins())); + bin_type operator[](int idx) const { + auto eval = [this](int i) { + const auto n = size(); + if (i < 0) + return transform::inverse(-std::numeric_limits::infinity()); + if (i > n) + return transform::inverse(std::numeric_limits::infinity()); + const auto z = value_type(i) / n; + return transform::inverse((1.0 - z) * min_ + + z * (min_ + delta_ * n)); + }; + return {eval(idx), eval(idx+1)}; } bool operator==(const regular &o) const { @@ -290,7 +291,7 @@ public: } const_iterator end() const { - return const_iterator(*this, uoflow() ? bins() + 1 : bins()); + return const_iterator(*this, uoflow() ? size() + 1 : size()); } private: @@ -311,6 +312,7 @@ class circular : public axis_base, boost::operators> { public: using value_type = RealType; + using bin_type = interval; using const_iterator = axis_iterator; /** Constructor for n bins with an optional offset. @@ -335,14 +337,17 @@ public: /// Returns the bin index for the passed argument. inline int index(value_type x) const noexcept { const value_type z = (x - phase_) / perimeter_; - const int i = static_cast(std::floor(z * bins())) % bins(); - return i + (i < 0) * bins(); + const int i = static_cast(std::floor(z * size())) % size(); + return i + (i < 0) * size(); } /// Returns the starting edge of the bin. - value_type operator[](int idx) const { - const value_type z = value_type(idx) / bins(); - return z * perimeter_ + phase_; + bin_type operator[](int idx) const { + auto eval = [this](int i) { + const value_type z = value_type(i) / size(); + return z * perimeter_ + phase_; + }; + return {eval(idx), eval(idx+1)}; } bool operator==(const circular &o) const { @@ -355,7 +360,7 @@ public: const_iterator begin() const { return const_iterator(*this, 0); } - const_iterator end() const { return const_iterator(*this, bins()); } + const_iterator end() const { return const_iterator(*this, size()); } private: value_type phase_ = 0.0, perimeter_ = 1.0; @@ -374,6 +379,7 @@ class variable : public axis_base, boost::operators> { public: using value_type = RealType; + using bin_type = interval; using const_iterator = axis_iterator; /** Construct an axis from bin edges. @@ -382,7 +388,7 @@ public: * \param label description of the axis. * \param uoflow whether to add under-/overflow bins. */ - variable(const std::initializer_list &x, + variable(std::initializer_list x, string_view label = string_view(), bool uoflow = true) : axis_base(x.size() - 1, label, uoflow), x_(new value_type[x.size()]) { @@ -390,7 +396,7 @@ public: throw std::logic_error("at least two values required"); } std::copy(x.begin(), x.end(), x_.get()); - std::sort(x_.get(), x_.get() + bins() + 1); + std::sort(x_.get(), x_.get() + size() + 1); } template @@ -399,19 +405,19 @@ public: : axis_base(std::distance(begin, end) - 1, label, uoflow), x_(new value_type[std::distance(begin, end)]) { std::copy(begin, end, x_.get()); - std::sort(x_.get(), x_.get() + bins() + 1); + std::sort(x_.get(), x_.get() + size() + 1); } variable() = default; variable(const variable &o) - : axis_base(o), x_(new value_type[bins() + 1]) { - std::copy(o.x_.get(), o.x_.get() + bins() + 1, x_.get()); + : axis_base(o), x_(new value_type[size() + 1]) { + std::copy(o.x_.get(), o.x_.get() + size() + 1, x_.get()); } variable &operator=(const variable &o) { if (this != &o) { axis_base::operator=(o); - x_.reset(new value_type[bins() + 1]); - std::copy(o.x_.get(), o.x_.get() + bins() + 1, x_.get()); + x_.reset(new value_type[size() + 1]); + std::copy(o.x_.get(), o.x_.get() + size() + 1, x_.get()); } return *this; } @@ -420,25 +426,28 @@ public: /// Returns the bin index for the passed argument. inline int index(value_type x) const noexcept { - return std::upper_bound(x_.get(), x_.get() + bins() + 1, x) - x_.get() - 1; + return std::upper_bound(x_.get(), x_.get() + size() + 1, x) - x_.get() - 1; } /// Returns the starting edge of the bin. - value_type operator[](int idx) const { - if (idx < 0) { - return -std::numeric_limits::infinity(); - } - if (idx > bins()) { - return std::numeric_limits::infinity(); - } - return x_[idx]; + bin_type operator[](int idx) const { + auto eval = [this](int i) { + if (i < 0) { + return -std::numeric_limits::infinity(); + } + if (i > size()) { + return std::numeric_limits::infinity(); + } + return x_[i]; + }; + return {eval(idx), eval(idx+1)}; } bool operator==(const variable &o) const { if (!axis_base::operator==(o)) { return false; } - return std::equal(x_.get(), x_.get() + bins() + 1, o.x_.get()); + return std::equal(x_.get(), x_.get() + size() + 1, o.x_.get()); } const_iterator begin() const { @@ -446,7 +455,7 @@ public: } const_iterator end() const { - return const_iterator(*this, uoflow() ? bins() + 1 : bins()); + return const_iterator(*this, uoflow() ? size() + 1 : size()); } private: @@ -466,6 +475,7 @@ class integer : public axis_base, boost::operators> { public: using value_type = IntType; + using bin_type = interval; using const_iterator = axis_iterator; /** Construct axis over integer range [min, max]. @@ -475,7 +485,7 @@ public: */ integer(value_type min, value_type max, string_view label = string_view(), bool uoflow = true) - : axis_base(max + 1 - min, label, uoflow), min_(min) { + : axis_base(max - min, label, uoflow), min_(min) { if (min > max) { throw std::logic_error("min <= max required"); } @@ -490,11 +500,13 @@ public: /// Returns the bin index for the passed argument. inline int index(value_type x) const noexcept { const int z = x - min_; - return z >= 0 ? (z > bins() ? bins() : z) : -1; + return z >= 0 ? (z > size() ? size() : z) : -1; } /// Returns the integer that is mapped to the bin index. - value_type operator[](int idx) const { return min_ + idx; } + bin_type operator[](int idx) const { + return {min_ + idx, min_ + idx + 1}; + } bool operator==(const integer &o) const { return axis_base::operator==(o) && min_ == o.min_; @@ -505,7 +517,7 @@ public: } const_iterator end() const { - return const_iterator(*this, uoflow() ? bins() + 1 : bins()); + return const_iterator(*this, uoflow() ? size() + 1 : size()); } private: @@ -515,84 +527,77 @@ private: template void serialize(Archive &, unsigned); }; -/** An axis for enumerated categories. +/** An axis for a set of unique values. * - * The axis stores the category labels, and expects that they - * are addressed using an integer from ``0`` to ``n-1``. - * There are no underflow/overflow bins for this axis. - * Binning is a O(1) operation. + * 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(1) operation. The value type must be hashable. */ -class category : public axis_base, boost::operators { +template +class category : public axis_base, boost::operators> { + using map_type = bimap; public: - using value_type = string_view; - using const_iterator = axis_iterator; - - template - category(Iterator begin, Iterator end, string_view label = string_view()) - : axis_base(std::distance(begin, end), label), - ptr_(new std::string[bins()]) { - std::copy(begin, end, ptr_.get()); - } - - /** Construct from a list of strings. - * - * \param categories sequence of labeled categories. - */ - category(const std::initializer_list &categories, - string_view label = string_view()) - : category(categories.begin(), categories.end(), label) {} + using value_type = T; + using bin_type = const value_type&; + using const_iterator = axis_iterator>; category() = default; - category(const category &other) - : category(other.ptr_.get(), other.ptr_.get() + other.bins(), - other.label()) {} - category &operator=(const category &other) { - if (this != &other) { - axis_base::operator=(other); - ptr_.reset(new std::string[other.bins()]); - std::copy(other.ptr_.get(), other.ptr_.get() + other.bins(), ptr_.get()); - } - return *this; + /** Construct from an initializer list of strings. + * + * \param seq sequence of unique values. + */ + category(std::initializer_list seq, + string_view label = string_view()) + : axis_base(seq.size(), label) + { + int index = 0; + for (const auto& x : seq) + map_.insert({x, index++}); + if (index == 0) + throw std::logic_error("sequence is empty"); } - category(category &&other) - : axis_base(std::move(other)), - ptr_(std::move(other.ptr_)) {} - - category &operator=(category &&other) { - if (this != &other) { - axis_base::operator=(std::move(other)); - ptr_ = std::move(other.ptr_); - } - return *this; + template + category(Iterator begin, Iterator end, + string_view label = string_view()) + : axis_base(std::distance(begin, end), label) + { + int index = 0; + while (begin != end) + map_.insert({*begin++, index++}); + if (index == 0) + throw std::logic_error("iterator range is empty"); } /// Returns the bin index for the passed argument. /// Performs a range check. - inline int index(int x) const noexcept { - BOOST_ASSERT_MSG(0 <= x && x < bins(), "category index is out of range"); - return x; + inline int index(const value_type& x) const noexcept { + auto it = map_.left.find(x); + if (it == map_.left.end()) + return size(); + return it->second; } - /// Returns the category for the bin index. - value_type operator[](int idx) const { - BOOST_ASSERT_MSG(0 <= idx && idx < bins(), - "category index is out of range"); - return ptr_.get()[idx]; + /// Returns the value for the bin index. + bin_type operator[](int idx) const { + auto it = map_.right.find(idx); + BOOST_ASSERT_MSG(it != map_.right.end(), "category index out of range"); + return it->second; } bool operator==(const category &other) const { return axis_base::operator==(other) && - std::equal(ptr_.get(), ptr_.get() + bins(), other.ptr_.get()); + std::equal(map_.begin(), map_.end(), other.map_.begin()); } const_iterator begin() const { return const_iterator(*this, 0); } - const_iterator end() const { return const_iterator(*this, bins()); } + const_iterator end() const { return const_iterator(*this, size()); } private: - std::unique_ptr ptr_; + map_type map_; friend class ::boost::serialization::access; template void serialize(Archive &, unsigned); @@ -602,7 +607,7 @@ private: using builtin_axes = mpl::vector, axis::circular<>, axis::variable<>, - axis::integer<>, axis::category>; + axis::integer<>, axis::category>; } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/axis_ostream_operators.hpp b/include/boost/histogram/axis_ostream_operators.hpp index 494ab135..85a662cb 100644 --- a/include/boost/histogram/axis_ostream_operators.hpp +++ b/include/boost/histogram/axis_ostream_operators.hpp @@ -18,7 +18,7 @@ namespace axis { template inline std::ostream &operator<<(std::ostream &os, const regular &a) { - os << "regular(" << a.bins() << ", " << a[0] << ", " << a[a.bins()]; + os << "regular(" << a.size() << ", " << a[0].lower() << ", " << a[a.size()].lower(); if (!a.label().empty()) { os << ", label="; ::boost::histogram::detail::escape(os, a.label()); @@ -32,7 +32,7 @@ inline std::ostream &operator<<(std::ostream &os, const regular &a) { template inline std::ostream &operator<<(std::ostream &os, const circular &a) { - os << "circular(" << a.bins(); + os << "circular(" << a.size(); if (a.phase() != 0.0) { os << ", phase=" << a.phase(); } @@ -49,9 +49,9 @@ inline std::ostream &operator<<(std::ostream &os, const circular &a) { template inline std::ostream &operator<<(std::ostream &os, const variable &a) { - os << "variable(" << a[0]; - for (int i = 1; i <= a.bins(); ++i) { - os << ", " << a[i]; + os << "variable(" << a[0].lower(); + for (int i = 1; i <= a.size(); ++i) { + os << ", " << a[i].lower(); } if (!a.label().empty()) { os << ", label="; @@ -66,7 +66,7 @@ inline std::ostream &operator<<(std::ostream &os, const variable &a) { template inline std::ostream &operator<<(std::ostream &os, const integer &a) { - os << "integer(" << a[0] << ", " << a[a.bins() - 1]; + os << "integer(" << a[0].lower() << ", " << a[a.size()].lower(); if (!a.label().empty()) { os << ", label="; ::boost::histogram::detail::escape(os, a.label()); @@ -78,11 +78,26 @@ inline std::ostream &operator<<(std::ostream &os, const integer &a) { return os; } -inline std::ostream &operator<<(std::ostream &os, const category &a) { +template +inline std::ostream &operator<<(std::ostream &os, const category &a) { os << "category("; - for (int i = 0; i < a.bins(); ++i) { + for (int i = 0; i < a.size(); ++i) { + os << a[i] << (i == (a.size() - 1) ? "" : ", "); + } + if (!a.label().empty()) { + os << ", label="; + ::boost::histogram::detail::escape(os, a.label()); + } + os << ")"; + return os; +} + +template <> +inline std::ostream &operator<<(std::ostream &os, const category &a) { + os << "category("; + for (int i = 0; i < a.size(); ++i) { ::boost::histogram::detail::escape(os, a[i]); - os << (i == (a.bins() - 1) ? "" : ", "); + os << (i == (a.size() - 1) ? "" : ", "); } if (!a.label().empty()) { os << ", label="; diff --git a/include/boost/histogram/detail/axis_visitor.hpp b/include/boost/histogram/detail/axis_visitor.hpp index 7a7aba12..59c5076b 100644 --- a/include/boost/histogram/detail/axis_visitor.hpp +++ b/include/boost/histogram/detail/axis_visitor.hpp @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Hans Dembinski +// Copyright 2015-2016 Hans Demsizeki // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -7,6 +7,7 @@ #ifndef _BOOST_HISTOGARM_AXIS_VISITOR_HPP_ #define _BOOST_HISTOGARM_AXIS_VISITOR_HPP_ +#include #include #include #include @@ -18,14 +19,14 @@ #include #include #include -#include +#include namespace boost { namespace histogram { namespace detail { -struct bins : public static_visitor { - template int operator()(const A &a) const { return a.bins(); } +struct size : public static_visitor { + template int operator()(const A &a) const { return a.size(); } }; struct shape : public static_visitor { @@ -42,34 +43,19 @@ template struct index : public static_visitor { template int operator()(const A &a) const { return a.index(v); } }; -struct left : public static_visitor { +struct bin : public static_visitor> { + using double_interval = interval; const int i; - explicit left(const int x) : i(x) {} - template double operator()(const A &a) const { - return impl(typename std::is_arithmetic::type(), - a[i]); - } - template double impl(std::true_type, const V &v) const { - return v; - } - template double impl(std::false_type, const V &) const { - throw std::runtime_error("cannot convert non-arithmetic type to double"); - } -}; - -struct right : public static_visitor { - const int i; - explicit right(const int x) : i(x) {} - template double operator()(const A &a) const { - return left(i + 1)(a); - } -}; - -struct center : public static_visitor { - const int i; - explicit center(const int x) : i(x) {} - template double operator()(const A &a) const { - return 0.5 * (left(i)(a) + right(i)(a)); + bin(const int v) : i(v) {} + template + double_interval operator()(const A &a) const { + return impl(is_convertible(), + std::forward(a[i])); } + template + double_interval impl(true_type, B && b) const { return b; } + template + double_interval impl(false_type, B &&) const { + throw std::runtime_error("cannot convert bin_type to interval"); } }; @@ -218,7 +204,7 @@ inline void axes_assign_impl(mpl::true_, mpl::true_, A &a, const A &b) { template inline void axes_assign_impl(mpl::true_, mpl::true_, A &a, const B &b) { - static_assert(std::is_same::type::value, + static_assert(is_same::type::value, "cannot assign different static axes vectors"); } diff --git a/include/boost/histogram/detail/utility.hpp b/include/boost/histogram/detail/utility.hpp index 09aa8347..a3f2f254 100644 --- a/include/boost/histogram/detail/utility.hpp +++ b/include/boost/histogram/detail/utility.hpp @@ -8,7 +8,7 @@ #define _BOOST_HISTOGRAM_DETAIL_UTILITY_HPP_ #include -#include +#include #include #include @@ -16,8 +16,7 @@ namespace boost { namespace histogram { namespace detail { -template -inline void escape(std::ostream &os, const String &s) { +inline void escape(std::ostream &os, const string_view s) { os << '\''; for (auto sit = s.begin(); sit != s.end(); ++sit) { if (*sit == '\'' && (sit == s.begin() || *(sit - 1) != '\\')) { @@ -37,8 +36,8 @@ inline void lin(std::size_t &out, std::size_t &stride, const A &a, const int uoflow = a.uoflow(); // set stride to zero if 'j' is not in range, // this communicates the out-of-range condition to the caller - stride *= (j >= -uoflow) & (j < (a.bins() + uoflow)); - j += (j < 0) * (a.bins() + 2); // wrap around if in < 0 + stride *= (j >= -uoflow) & (j < (a.size() + uoflow)); + j += (j < 0) * (a.size() + 2); // wrap around if in < 0 out += j * stride; #pragma GCC diagnostic ignored "-Wstrict-overflow" stride *= a.shape(); @@ -51,7 +50,7 @@ inline void xlin(std::size_t &out, std::size_t &stride, const A &a, // please measure the performance impact of changes int j = a.index(std::forward(x)); // j is guaranteed to be in range [-1, bins] - j += (j < 0) * (a.bins() + 2); // wrap around if j < 0 + j += (j < 0) * (a.size() + 2); // wrap around if j < 0 out += j * stride; #pragma GCC diagnostic ignored "-Wstrict-overflow" stride *= (j < a.shape()) * a.shape(); // stride == 0 indicates out-of-range diff --git a/include/boost/histogram/serialization.hpp b/include/boost/histogram/serialization.hpp index 85ec19a1..105354bd 100644 --- a/include/boost/histogram/serialization.hpp +++ b/include/boost/histogram/serialization.hpp @@ -120,13 +120,13 @@ void adaptive_storage::serialize(Archive &ar, namespace axis { template -void axis_base::serialize(Archive &ar, unsigned /* version */) { +void axis_base::serialize(Archive &ar, unsigned /* version */) { ar &size_; ar &label_; } template -void axis_base::serialize(Archive &ar, unsigned /* version */) { +void axis_base::serialize(Archive &ar, unsigned /* version */) { ar &size_; ar &shape_; ar &label_; @@ -136,7 +136,7 @@ template class Transform> template void regular::serialize(Archive &ar, unsigned /* version */) { - ar &boost::serialization::base_object>(*this); + ar &boost::serialization::base_object>(*this); ar &min_; ar &delta_; } @@ -144,7 +144,7 @@ void regular::serialize(Archive &ar, template template void circular::serialize(Archive &ar, unsigned /* version */) { - ar &boost::serialization::base_object>(*this); + ar &boost::serialization::base_object>(*this); ar &phase_; ar &perimeter_; } @@ -152,27 +152,25 @@ void circular::serialize(Archive &ar, unsigned /* version */) { template template void variable::serialize(Archive &ar, unsigned /* version */) { - ar &boost::serialization::base_object>(*this); + ar &boost::serialization::base_object>(*this); if (Archive::is_loading::value) { - x_.reset(new RealType[bins() + 1]); + x_.reset(new RealType[size() + 1]); } - ar &boost::serialization::make_array(x_.get(), bins() + 1); + ar &boost::serialization::make_array(x_.get(), size() + 1); } template template void integer::serialize(Archive &ar, unsigned /* version */) { - ar &boost::serialization::base_object>(*this); + ar &boost::serialization::base_object>(*this); ar &min_; } +template template -void category::serialize(Archive &ar, unsigned /* version */) { - ar &boost::serialization::base_object>(*this); - if (Archive::is_loading::value) { - ptr_.reset(new std::string[bins()]); - } - ar &boost::serialization::make_array(ptr_.get(), bins()); +void category::serialize(Archive &ar, unsigned /* version */) { + ar &boost::serialization::base_object>(*this); + ar &map_; } } // namespace axis diff --git a/include/boost/histogram/utility.hpp b/include/boost/histogram/utility.hpp index 7a65a39e..1d761f89 100644 --- a/include/boost/histogram/utility.hpp +++ b/include/boost/histogram/utility.hpp @@ -13,10 +13,10 @@ namespace boost { namespace histogram { -template inline int bins(const A &a) { return a.bins(); } +template inline int size(const A &a) { return a.size(); } -template inline int bins(const boost::variant &a) { - return apply_visitor(detail::bins(), a); +template inline int size(const boost::variant &a) { + return apply_visitor(detail::size(), a); } template inline int shape(const A &a) { return a.shape(); } @@ -35,32 +35,13 @@ inline int index(const boost::variant &a, const V v) { } template -inline typename A::value_type left(const A &a, const int i) { +inline typename A::bin_type bin(const A &a, const int i) { return a[i]; } template -inline double left(const boost::variant &a, const int i) { - return apply_visitor(detail::left(i), a); -} - -template -inline typename A::value_type right(const A &a, const int i) { - return left(a, i + 1); -} - -template -inline double right(const boost::variant &a, const int i) { - return apply_visitor(detail::right(i), a); -} - -template inline double center(const A &a, const int i) { - return 0.5 * (left(a, i) + right(a, i)); -} - -template -inline double center(const boost::variant &a, const int i) { - return apply_visitor(detail::center(i), a); +inline interval bin(const boost::variant &a, const int i) { + return apply_visitor(detail::bin(i), a); } } // namespace histogram diff --git a/src/python/axis.cpp b/src/python/axis.cpp index d3b3cd0f..2c20a0ce 100644 --- a/src/python/axis.cpp +++ b/src/python/axis.cpp @@ -10,10 +10,17 @@ #include #include #include +#ifdef HAVE_NUMPY +#define NO_IMPORT_ARRAY +#define PY_ARRAY_UNIQUE_SYMBOL boost_histogram_ARRAY_API +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include +#endif #include #include #include #include +#include namespace boost { namespace histogram { @@ -82,31 +89,27 @@ python::object category_init(python::tuple args, python::dict kwargs) { } } - std::vector c; + std::vector c; for (int i = 1, n = len(args); i < n; ++i) - c.push_back(extract(args[i])); + c.push_back(extract(args[i])); - return self.attr("__init__")(axis::category(c.begin(), c.end(), label)); + return self.attr("__init__")(axis::category<>(c.begin(), c.end(), label)); } -template int axis_len(const T &t) { - return t.bins() + int(std::is_floating_point::value); -} - -template python::object axis_getitem(const T &t, int i) { - if (i == axis_len(t)) { +template python::object axis_getitem(const A &a, int i) { + if (i == a.size()) { PyErr_SetString(PyExc_StopIteration, "no more"); python::throw_error_already_set(); } - return python::object(t[i]); + return python::object(a[i]); } -template <> python::object axis_getitem(const axis::category &a, int i) { - if (i == axis_len(a)) { +template <> python::object axis_getitem(const axis::category<> &a, int i) { + if (i == a.size()) { PyErr_SetString(PyExc_StopIteration, "no more"); python::throw_error_already_set(); } - return python::object(a[i].data()); + return python::object(a[i]); } template std::string axis_repr(const T &t) { @@ -126,31 +129,75 @@ template python::str axis_get_label(const T& t) { return {s.data(), s.size()}; } +#ifdef HAVE_NUMPY +template python::object axis_array_interface(const Axis& axis) { + python::dict d; + auto shape = python::make_tuple(axis.size()+1); + d["shape"] = shape; + // d["typestr"] = dtype_typestr(); + d["typestr"] = "|f8"; + // make new array, and pass it to Python + auto dim = 1; + npy_intp shapes2[1] = { axis.size()+1 }; + auto *a = (PyArrayObject*)PyArray_SimpleNew(dim, shapes2, NPY_DOUBLE); + auto *buf = (double *)PyArray_DATA(a); + PyArray_CLEARFLAGS(a, NPY_ARRAY_WRITEABLE); + // auto a = python::numpy::empty(shape, python::numpy::dtype::get_builtin()); + // auto buf = reinterpret_cast(axis.get_data()); + for (auto i = 0; i < axis.size()+1; ++i) + buf[i] = axis[i].lower(); + d["data"] = python::object(python::handle<>((PyObject*)a)); + d["version"] = 3; + return d; +} + +template <> python::object axis_array_interface>(const axis::category<>& axis) { + python::dict d; + auto shape = python::make_tuple(axis.size()); + d["shape"] = shape; + // d["typestr"] = dtype_typestr(); + d["typestr"] = "|i4"; + // make new array, and pass it to Python + auto dim = 1; + npy_intp shapes2[1] = { axis.size() }; + auto *a = (PyArrayObject*)PyArray_SimpleNew(dim, shapes2, NPY_INT); + auto *buf = (int *)PyArray_DATA(a); + PyArray_CLEARFLAGS(a, NPY_ARRAY_WRITEABLE); + // auto a = python::numpy::empty(shape, python::numpy::dtype::get_builtin()); + // auto buf = reinterpret_cast(axis.get_data()); + for (auto i = 0; i < axis.size(); ++i) + buf[i] = axis[i]; + d["data"] = python::object(python::handle<>((PyObject*)a)); + d["version"] = 3; + return d; +} +#endif + template struct axis_suite : public python::def_visitor> { template static void visit(Class &cl) { - cl.add_property("bins", &T::bins, "Number of bins."); cl.add_property( "shape", &T::shape, - "Number of bins, including possible over- and underflow bins."); + "Number of bins, including over-/underflow bins if they are present."); cl.add_property( "label", axis_get_label, axis_set_label, "Name or description for the axis."); cl.def("index", &T::index, ":param float x: value" "\n:returns: bin index for the passed value", python::args("self", "x")); - cl.def("__len__", axis_len, ":returns: number of bins for this axis", + cl.def("__len__", &T::size, + ":returns: number of bins, excluding over-/underflow bins.", python::arg("self")); cl.def("__getitem__", axis_getitem, - is_same>::value - ? ":returns: integer mapped to passed bin index" - : is_same::value - ? ":returns: category mapped to passed bin index" - : ":returns: low edge of the bin", - python::args("self", "index")); + ":param integer i: bin index" + "\n:returns: bin corresponding to index", + python::args("self", "i")); cl.def("__repr__", axis_repr, ":returns: string representation of this axis", python::arg("self")); cl.def(python::self == python::self); +#ifdef HAVE_NUMPY + cl.add_property("__array_interface__", &axis_array_interface); +#endif } }; @@ -161,12 +208,35 @@ void register_axis_types() { using python::arg; docstring_options dopt(true, true, false); - class_>("regular", - "An axis for real-valued data and bins of equal width." - "\nBinning is a O(1) operation.", - no_init) + class_>( + "interval_double", + no_init) + .add_property("lower", + make_function(&interval::lower, + return_value_policy())) + .add_property("upper", + make_function(&interval::upper, + return_value_policy())) + ; + + class_>( + "interval_int", + no_init) + .add_property("lower", + make_function(&interval::lower, + return_value_policy())) + .add_property("upper", + make_function(&interval::upper, + return_value_policy())) + ; + + class_>( + "regular", + "An axis for real-valued data and bins of equal width." + "\nBinning is a O(1) operation.", + no_init) .def(init( - (arg("self"), arg("bin"), arg("min"), arg("max"), + (arg("self"), arg("bin"), arg("lower"), arg("upper"), arg("label") = std::string(), arg("uoflow") = true))) .def(axis_suite>()); @@ -187,32 +257,33 @@ void register_axis_types() { "variable", "An axis for real-valued data and bins of varying width." "\nBinning is a O(log(N)) operation. If speed matters and" - "\nthe problem domain allows it, prefer a regular.", + "\nthe problem domain allows it, prefer a regular axis.", no_init) .def("__init__", raw_function(variable_init)) .def(init &>()) .def(axis_suite>()); - class_>("integer", - "An axis for a contiguous range of integers." - "\nThere are no underflow/overflow bins for this axis." - "\nBinning is a O(1) operation.", - no_init) + class_>( + "integer", + "An axis for a contiguous range of integers with bins" + "\nthat are one integer wide. Faster than a regular axis." + "\nBinning is a O(1) operation.", + no_init) .def(init( - (arg("self"), arg("min"), arg("max"), arg("label") = std::string(), + (arg("self"), arg("lower"), arg("upper"), arg("label") = std::string(), arg("uoflow") = true))) .def(axis_suite>()); - class_("category", - "An axis for enumerated categories. The axis stores the" - "\ncategory labels, and expects that they are addressed" - "\nusing an integer from 0 to n-1. There are no" - "\nunderflow/overflow bins for this axis." - "\nBinning is a O(1) operation.", - no_init) + class_>( + "category", + "An axis for set of unique integer values. Each value is mapped to" + "\na corresponding bin, following the order of the arguments in" + "\nthe constructor." + "\nBinning is a O(1) operation.", + no_init) .def("__init__", raw_function(category_init)) - .def(init()) - .def(axis_suite()); + .def(init &>()) + .def(axis_suite>()); } } } diff --git a/src/python/histogram.cpp b/src/python/histogram.cpp index c43caa9b..2eaa669a 100644 --- a/src/python/histogram.cpp +++ b/src/python/histogram.cpp @@ -205,7 +205,7 @@ python::object histogram_init(python::tuple args, python::dict kwargs) { axes.push_back(ei()); continue; } - python::extract ec(pa); + python::extract> ec(pa); if (ec.check()) { axes.push_back(ec()); continue; @@ -354,7 +354,7 @@ python::object histogram_value(python::tuple args, python::dict kwargs) { for (unsigned i = 0; i < self.dim(); ++i) idx[i] = python::extract(args[1 + i]); - return python::object(self.value(idx + 0, idx + self.dim())); + return python::object(self.value(idx, idx + self.dim())); } python::object histogram_variance(python::tuple args, python::dict kwargs) { @@ -383,7 +383,7 @@ python::object histogram_variance(python::tuple args, python::dict kwargs) { for (unsigned i = 0; i < self.dim(); ++i) idx[i] = python::extract(args[1 + i]); - return python::object(self.variance(idx + 0, idx + self.dim())); + return python::object(self.variance(idx, idx + self.dim())); } std::string histogram_repr(const dynamic_histogram &h) { diff --git a/src/python/serialization_suite.hpp b/src/python/serialization_suite.hpp index 8249d35e..56b08415 100644 --- a/src/python/serialization_suite.hpp +++ b/src/python/serialization_suite.hpp @@ -4,8 +4,8 @@ // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#ifndef _BOOST_PYTHON_SERIALIZATION_SUITE_HPP_ -#define _BOOST_PYTHON_SERIALIZATION_SUITE_HPP_ +#ifndef _BOOST_HISTOGRAM_PYTHON_SERIALIZATION_SUITE_HPP_ +#define _BOOST_HISTOGRAM_PYTHON_SERIALIZATION_SUITE_HPP_ #include #include diff --git a/test/axis_test.cpp b/test/axis_test.cpp index d4628292..15e7153b 100644 --- a/test/axis_test.cpp +++ b/test/axis_test.cpp @@ -20,22 +20,20 @@ #define BOOST_TEST_NOT(expr) BOOST_TEST(!(expr)) #define BOOST_TEST_IS_CLOSE(a, b, eps) BOOST_TEST(std::abs(a - b) < eps) -template -void test_real_axis_iterator(Axis &&a, int begin, int end) { - for (const auto &bin : a) { - BOOST_TEST_EQ(bin.idx, begin); - BOOST_TEST_EQ(bin.left, boost::histogram::left(a, begin)); - BOOST_TEST_EQ(bin.right, boost::histogram::right(a, begin)); - ++begin; - } - BOOST_TEST_EQ(begin, end); +namespace boost { namespace histogram { +template +std::ostream& operator<<(std::ostream& os, const interval& i) +{ + os << "[" << i.lower() << ", " << i.upper() << ")"; + return os; } +}} template void test_axis_iterator(const Axis &a, int begin, int end) { for (const auto &bin : a) { - BOOST_TEST_EQ(bin.idx, begin); - BOOST_TEST_EQ(bin.value, a[begin]); + BOOST_TEST_EQ(bin.first, begin); + BOOST_TEST_EQ(bin.second, a[begin]); ++begin; } BOOST_TEST_EQ(begin, end); @@ -53,14 +51,14 @@ int main() { 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); + BOOST_TEST_THROWS(axis::category<>({}), std::logic_error); } // axis::regular { axis::regular<> a{4, -2, 2}; - BOOST_TEST_EQ(a[-1], -std::numeric_limits::infinity()); - BOOST_TEST_EQ(a[a.bins() + 1], std::numeric_limits::infinity()); + 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; @@ -90,11 +88,11 @@ int main() { // axis::regular with transform { axis::regular b{2, 1e0, 1e2}; - BOOST_TEST_EQ(b[-1], 0.0); - BOOST_TEST_IS_CLOSE(b[0], 1.0, 1e-9); - BOOST_TEST_IS_CLOSE(b[1], 10.0, 1e-9); - BOOST_TEST_IS_CLOSE(b[2], 100.0, 1e-9); - BOOST_TEST_EQ(b[3], std::numeric_limits::infinity()); + 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), -1); BOOST_TEST_EQ(b.index(0), -1); @@ -109,7 +107,7 @@ int main() { // axis::circular { axis::circular<> a{4}; - BOOST_TEST_EQ(a[-1], a[a.bins() - 1] - a.perimeter()); + BOOST_TEST_EQ(a[-1].lower(), a[a.size() - 1].lower() - a.perimeter()); axis::circular<> b; BOOST_TEST_NOT(a == b); b = a; @@ -137,8 +135,8 @@ int main() { // axis::variable { axis::variable<> a{-1, 0, 1}; - BOOST_TEST_EQ(a[-1], -std::numeric_limits::infinity()); - BOOST_TEST_EQ(a[a.bins() + 1], std::numeric_limits::infinity()); + 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; @@ -166,7 +164,7 @@ int main() { // axis::integer { - axis::integer<> a{-1, 1}; + axis::integer<> a{-1, 2}; axis::integer<> b; BOOST_TEST_NOT(a == b); b = a; @@ -191,35 +189,41 @@ int main() { // axis::category { - axis::category a{{"A", "B", "C"}}; - axis::category b; + std::string A("A"), B("B"), C("C"); + axis::category a{{A, B, C}}; + axis::category b; BOOST_TEST_NOT(a == b); b = a; BOOST_TEST_EQ(a, b); b = b; BOOST_TEST_EQ(a, b); - axis::category c = std::move(b); + axis::category c = std::move(b); BOOST_TEST(c == a); BOOST_TEST_NOT(b == a); - axis::category d; + axis::category d; BOOST_TEST_NOT(c == d); d = std::move(c); BOOST_TEST_EQ(d, a); - BOOST_TEST_EQ(a.index(0), 0); - BOOST_TEST_EQ(a.index(1), 1); - BOOST_TEST_EQ(a.index(2), 2); + 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[0], A); + BOOST_TEST_EQ(a[1], B); + BOOST_TEST_EQ(a[2], C); } // iterators { - test_real_axis_iterator(axis::regular<>(5, 0, 1, "", false), 0, 5); - test_real_axis_iterator(axis::regular<>(5, 0, 1, "", true), -1, 6); - test_real_axis_iterator(axis::circular<>(5, 0, 1, ""), 0, 5); - test_real_axis_iterator(axis::variable<>({1, 2, 3}, "", false), 0, 2); - test_real_axis_iterator(axis::variable<>({1, 2, 3}, "", true), -1, 3); - test_axis_iterator(axis::integer<>(0, 4, "", false), 0, 5); - test_axis_iterator(axis::integer<>(0, 4, "", true), -1, 6); - test_axis_iterator(axis::category({"A", "B", "C"}), 0, 3); + enum {A, B, C}; + test_axis_iterator(axis::regular<>(5, 0, 1, "", false), 0, 5); + test_axis_iterator(axis::regular<>(5, 0, 1, "", true), -1, 6); + test_axis_iterator(axis::circular<>(5, 0, 1, ""), 0, 5); + test_axis_iterator(axis::variable<>({1, 2, 3}, "", false), 0, 2); + test_axis_iterator(axis::variable<>({1, 2, 3}, "", true), -1, 3); + test_axis_iterator(axis::integer<>(0, 4, "", false), 0, 4); + test_axis_iterator(axis::integer<>(0, 4, "", true), -1, 5); + test_axis_iterator(axis::category<>({A, B, C}, ""), 0, 3); } // axis_t_copyable @@ -247,11 +251,12 @@ int main() { // axis_t_streamable { + enum {A, B, C}; std::vector axes; axes.push_back(axis::regular<>{2, -1, 1, "regular", false}); axes.push_back(axis::circular<>{4, 0.1, 1.0, "polar"}); axes.push_back(axis::variable<>{{-1, 0, 1}, "variable", false}); - axes.push_back(axis::category{{"A", "B", "C"}, "category"}); + axes.push_back(axis::category<>{{A, B, C}, "category"}); axes.push_back(axis::integer<>{-1, 1, "integer", false}); std::ostringstream os; for (const auto &a : axes) { @@ -260,18 +265,19 @@ int main() { const std::string ref = "regular(2, -1, 1, label='regular', uoflow=False)" "circular(4, phase=0.1, perimeter=1, label='polar')" "variable(-1, 0, 1, label='variable', uoflow=False)" - "category('A', 'B', 'C', label='category')" + "category(0, 1, 2, label='category')" "integer(-1, 1, label='integer', uoflow=False)"; BOOST_TEST_EQ(os.str(), ref); } // axis_t_equal_comparable { + enum {A, B, C}; std::vector axes; axes.push_back(axis::regular<>{2, -1, 1}); 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::category<>{A, B, C}); axes.push_back(axis::integer<>{-1, 1}); for (const auto &a : axes) { BOOST_TEST(!(a == axis_t())); @@ -283,15 +289,19 @@ int main() { // sequence equality { + enum {A, B, C}; std::vector, axis::variable<>, - axis::category, axis::integer<>>> + axis::category<>, axis::integer<>>> std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B", "C"}}; + axis::category<>{A, B, C}}; std::vector< - boost::variant, axis::variable<>, axis::category>> - std_vector2 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B", "C"}}; + boost::variant< + axis::regular<>, 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}}; @@ -305,11 +315,11 @@ int main() { auto fusion_vector1 = boost::fusion::make_vector( axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B", "C"}); + axis::category<>{{A, B, C}}); auto fusion_vector2 = boost::fusion::make_vector(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B"}); + axis::category<>{{A, B}}); auto fusion_vector3 = boost::fusion::make_vector( axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}); @@ -323,22 +333,23 @@ int main() { // sequence assign { + enum {A, B, C, D}; std::vector, axis::variable<>, - axis::category, axis::integer<>>> + axis::category<>, axis::integer<>>> std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B", "C"}}; + axis::category<>{A, B, C}}; std::vector< - boost::variant, axis::variable<>, axis::category>> + boost::variant, axis::variable<>, axis::category<>>> std_vector2 = {axis::regular<>{2, -2, 2}, axis::variable<>{-2, 0, 2}, - axis::category{"A", "B"}}; + axis::category<>{A, B}}; detail::axes_assign(std_vector2, std_vector1); BOOST_TEST(detail::axes_equal(std_vector2, std_vector1)); auto fusion_vector1 = boost::fusion::make_vector( axis::regular<>{2, -3, 3}, axis::variable<>{-3, 0, 3}, - axis::category{"A", "B", "C", "D"}); + axis::category<>{A, B, C, D}); detail::axes_assign(fusion_vector1, std_vector1); BOOST_TEST(detail::axes_equal(fusion_vector1, std_vector1)); @@ -350,7 +361,7 @@ int main() { auto fusion_vector2 = boost::fusion::make_vector(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B"}); + axis::category<>{A, B}); detail::axes_assign(fusion_vector2, fusion_vector1); BOOST_TEST(detail::axes_equal(fusion_vector2, fusion_vector1)); diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index d4d271cf..45529a81 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -77,25 +77,25 @@ template void run_tests() { // init_2 { auto h = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}); + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}); BOOST_TEST_EQ(h.dim(), 2); BOOST_TEST_EQ(h.size(), 25); BOOST_TEST_EQ(shape(h.axis(0_c)), 5); BOOST_TEST_EQ(shape(h.axis(1_c)), 5); auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}); + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}); BOOST_TEST(h2 == h); } // init_3 { auto h = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}, + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, axis::circular<>{3}); BOOST_TEST_EQ(h.dim(), 3); BOOST_TEST_EQ(h.size(), 75); auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}, + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, axis::circular<>{3}); BOOST_TEST(h2 == h); } @@ -103,35 +103,36 @@ template void run_tests() { // init_4 { auto h = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}, + Type(), 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); auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}, + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, axis::circular<>{3}, axis::variable<>{-1, 0, 1}); BOOST_TEST(h2 == h); } // init_5 { + enum {A, B, C}; auto h = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}, + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, axis::circular<>{3}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B", "C"}); + axis::category<>{{A, B, C}}); BOOST_TEST_EQ(h.dim(), 5); BOOST_TEST_EQ(h.size(), 900); auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 1}, + Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, axis::circular<>{3}, axis::variable<>{-1, 0, 1}, - axis::category{"A", "B", "C"}); + axis::category<>{{A, B, C}}); BOOST_TEST(h2 == h); } // copy_ctor { - auto h = make_histogram>(Type(), axis::integer<>(0, 1), - axis::integer<>(0, 2)); + auto h = make_histogram>(Type(), axis::integer<>{0, 2}, + axis::integer<>{0, 3}); h.fill(0, 0); auto h2 = decltype(h)(h); BOOST_TEST(h2 == h); @@ -183,30 +184,28 @@ template void run_tests() { { auto a = make_histogram>(Type(), axis::regular<>(1, 1, 2)); - BOOST_TEST_EQ(bins(a.axis()), 1); + BOOST_TEST_EQ(size(a.axis()), 1); BOOST_TEST_EQ(shape(a.axis()), 3); BOOST_TEST_EQ(index(a.axis(), 1.0), 0); - BOOST_TEST_EQ(left(a.axis(), 0), 1.0); - BOOST_TEST_EQ(right(a.axis(), 0), 2.0); - BOOST_TEST_EQ(center(a.axis(), 0), 1.5); + BOOST_TEST_EQ(bin(a.axis(), 0).lower(), 1.0); + BOOST_TEST_EQ(bin(a.axis(), 0).upper(), 2.0); auto b = make_histogram>(Type(), axis::integer<>(1, 2)); - BOOST_TEST_EQ(bins(a.axis()), 1); + BOOST_TEST_EQ(size(a.axis()), 1); BOOST_TEST_EQ(shape(a.axis()), 3); BOOST_TEST_EQ(index(a.axis(), 1.0), 0); - BOOST_TEST_EQ(left(b.axis(), 0), 1.0); - BOOST_TEST_EQ(right(b.axis(), 0), 2.0); - BOOST_TEST_EQ(center(b.axis(), 0), 1.5); + BOOST_TEST_EQ(bin(b.axis(), 0).lower(), 1.0); + BOOST_TEST_EQ(bin(b.axis(), 0).upper(), 2.0); } // equal_compare { - auto a = make_histogram>(Type(), axis::integer<>(0, 1)); - auto b = make_histogram>(Type(), axis::integer<>(0, 1), - axis::integer<>(0, 2)); + auto a = make_histogram>(Type(), axis::integer<>(0, 2)); + auto b = make_histogram>(Type(), axis::integer<>(0, 2), + axis::integer<>(0, 3)); BOOST_TEST(a != b); BOOST_TEST(b != a); - auto c = make_histogram>(Type(), axis::integer<>(0, 1)); + auto c = make_histogram>(Type(), axis::integer<>(0, 2)); BOOST_TEST(b != c); BOOST_TEST(c != b); BOOST_TEST(a == c); @@ -228,14 +227,14 @@ template void run_tests() { // d1 { - auto h = make_histogram>(Type(), axis::integer<>(0, 1)); + auto h = make_histogram>(Type(), axis::integer<>{0, 2}); h.fill(0); h.fill(0); h.fill(-1); h.fill(10, count(10)); BOOST_TEST_EQ(h.dim(), 1); - BOOST_TEST_EQ(bins(h.axis(0_c)), 2); + BOOST_TEST_EQ(size(h.axis(0_c)), 2); BOOST_TEST_EQ(shape(h.axis(0_c)), 4); BOOST_TEST_EQ(h.sum(), 13); @@ -257,14 +256,14 @@ template void run_tests() { // d1_2 { auto h = make_histogram>( - Type(), axis::integer<>(0, 1, "", false)); + Type(), axis::integer<>(0, 2, "", false)); h.fill(0); h.fill(-0); h.fill(-1); h.fill(10, count(10)); BOOST_TEST_EQ(h.dim(), 1); - BOOST_TEST_EQ(bins(h.axis(0_c)), 2); + BOOST_TEST_EQ(size(h.axis(0_c)), 2); BOOST_TEST_EQ(shape(h.axis(0_c)), 2); BOOST_TEST_EQ(h.sum(), 2); @@ -323,16 +322,16 @@ template void run_tests() { // d2 { auto h = make_histogram>( - Type(), axis::regular<>(2, -1, 1), axis::integer<>(-1, 1, "", false)); + Type(), axis::regular<>(2, -1, 1), axis::integer<>(-1, 2, "", false)); h.fill(-1, -1); h.fill(-1, 0); h.fill(-1, -10); h.fill(-10, 0); BOOST_TEST_EQ(h.dim(), 2); - BOOST_TEST_EQ(bins(h.axis(0_c)), 2); + BOOST_TEST_EQ(size(h.axis(0_c)), 2); BOOST_TEST_EQ(shape(h.axis(0_c)), 4); - BOOST_TEST_EQ(bins(h.axis(1_c)), 3); + BOOST_TEST_EQ(size(h.axis(1_c)), 3); BOOST_TEST_EQ(shape(h.axis(1_c)), 3); BOOST_TEST_EQ(h.sum(), 3); @@ -372,7 +371,7 @@ template void run_tests() { // d2w { auto h = make_histogram>( - Type(), axis::regular<>(2, -1, 1), axis::integer<>(-1, 1, "", false)); + Type(), axis::regular<>(2, -1, 1), axis::integer<>(-1, 2, "", false)); h.fill(-1, 0); // -> 0, 1 h.fill(weight(10), -1, -1); // -> 0, 0 h.fill(weight(5), -1, -10); // is ignored @@ -418,17 +417,17 @@ template void run_tests() { auto h = make_histogram>(Type(), axis::integer<>(0, 3), axis::integer<>(0, 4), axis::integer<>(0, 5)); - for (auto i = 0; i < bins(h.axis(0_c)); ++i) { - for (auto j = 0; j < bins(h.axis(1_c)); ++j) { - for (auto k = 0; k < bins(h.axis(2_c)); ++k) { + for (auto i = 0; i < size(h.axis(0_c)); ++i) { + for (auto j = 0; j < size(h.axis(1_c)); ++j) { + for (auto k = 0; k < size(h.axis(2_c)); ++k) { h.fill(weight(i + j + k), i, j, k); } } } - for (auto i = 0; i < bins(h.axis(0_c)); ++i) { - for (auto j = 0; j < bins(h.axis(1_c)); ++j) { - for (auto k = 0; k < bins(h.axis(2_c)); ++k) { + for (auto i = 0; i < size(h.axis(0_c)); ++i) { + for (auto j = 0; j < size(h.axis(1_c)); ++j) { + for (auto k = 0; k < size(h.axis(2_c)); ++k) { BOOST_TEST_EQ(h.value(i, j, k), i + j + k); } } @@ -437,9 +436,9 @@ template void run_tests() { // add_1 { - auto a = make_histogram>(Type(), axis::integer<>(-1, 1)); + auto a = make_histogram>(Type(), axis::integer<>(-1, 2)); auto b = - make_histogram>(Type(), axis::integer<>(-1, 1)); + make_histogram>(Type(), axis::integer<>(-1, 2)); a.fill(-1); b.fill(1); auto c = a; @@ -460,8 +459,8 @@ template void run_tests() { // add_2 { - auto a = make_histogram>(Type(), axis::integer<>(0, 1)); - auto b = make_histogram>(Type(), axis::integer<>(0, 1)); + auto a = make_histogram>(Type(), axis::integer<>(0, 2)); + auto b = make_histogram>(Type(), axis::integer<>(0, 2)); a.fill(0); BOOST_TEST_EQ(a.variance(0), 1); @@ -488,9 +487,9 @@ template void run_tests() { // add_3 { auto a = - make_histogram>(Type(), axis::integer<>(-1, 1)); + make_histogram>(Type(), axis::integer<>(-1, 2)); auto b = - make_histogram>(Type(), axis::integer<>(-1, 1)); + make_histogram>(Type(), axis::integer<>(-1, 2)); a.fill(-1); b.fill(1); auto c = a; @@ -511,14 +510,14 @@ template void run_tests() { // bad_add { - auto a = make_histogram>(Type(), axis::integer<>(0, 1)); - auto b = make_histogram>(Type(), axis::integer<>(0, 2)); + auto a = make_histogram>(Type(), axis::integer<>(0, 2)); + auto b = make_histogram>(Type(), axis::integer<>(0, 3)); BOOST_TEST_THROWS(a += b, std::logic_error); } // bad_index { - auto a = make_histogram>(Type(), axis::integer<>(0, 1)); + auto a = make_histogram>(Type(), axis::integer<>(0, 2)); BOOST_TEST_THROWS(a.value(5), std::out_of_range); BOOST_TEST_THROWS(a.variance(5), std::out_of_range); } @@ -526,19 +525,20 @@ template void run_tests() { // functional programming { auto v = std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - auto h = make_histogram>(Type(), axis::integer<>(0, 9)); + auto h = make_histogram>(Type(), axis::integer<>(0, 10)); std::for_each(v.begin(), v.end(), [&h](int x) { h.fill(weight(2.0), x); }); BOOST_TEST_EQ(h.sum(), 20.0); } // histogram_serialization { + enum { A, B, C }; auto a = make_histogram>( Type(), axis::regular<>(3, -1, 1, "r"), axis::circular<>(4, 0.0, 1.0, "p"), axis::regular(3, 1, 100, "lr"), axis::variable<>({0.1, 0.2, 0.3, 0.4, 0.5}, "v"), - axis::category{"A", "B", "C"}, axis::integer<>(0, 1, "i")); + axis::category<>{A, B, C}, axis::integer<>(0, 2, "i")); a.fill(0.5, 20, 0.1, 0.25, 1, 0); std::string buf; { @@ -560,19 +560,19 @@ template void run_tests() { // histogram_ostream { auto a = make_histogram>( - Type(), axis::regular<>(3, -1, 1, "r"), axis::integer<>(0, 1, "i")); + Type(), axis::regular<>(3, -1, 1, "r"), axis::integer<>(0, 2, "i")); std::ostringstream os; os << a; BOOST_TEST_EQ(os.str(), "histogram(" "\n regular(3, -1, 1, label='r')," - "\n integer(0, 1, label='i')," + "\n integer(0, 2, label='i')," "\n)"); } // histogram_reset { auto a = make_histogram>( - Type(), axis::integer<>(0, 1, "", false)); + Type(), axis::integer<>(0, 2, "", false)); a.fill(0); a.fill(1); BOOST_TEST_EQ(a.value(0), 1); @@ -584,8 +584,8 @@ template void run_tests() { // reduce { - auto h1 = make_histogram>(Type(), axis::integer<>(0, 1), - axis::integer<>(0, 2)); + auto h1 = make_histogram>(Type(), axis::integer<>(0, 2), + axis::integer<>(0, 3)); h1.fill(0, 0); h1.fill(0, 1); h1.fill(1, 0); @@ -597,8 +597,8 @@ template void run_tests() { BOOST_TEST_EQ(h1_0.sum(), 5); BOOST_TEST_EQ(h1_0.value(0), 2); BOOST_TEST_EQ(h1_0.value(1), 3); - BOOST_TEST_EQ(left(h1_0.axis(), 0), 0.0); - BOOST_TEST_EQ(left(h1_0.axis(), 1), 1.0); + BOOST_TEST_EQ(bin(h1_0.axis(), 0).lower(), 0.0); + BOOST_TEST_EQ(bin(h1_0.axis(), 1).lower(), 1.0); BOOST_TEST(axis_equal(Type(), h1_0.axis(), h1.axis(0_c))); auto h1_1 = reduce(h1, keep(1_c)); @@ -609,9 +609,9 @@ template void run_tests() { BOOST_TEST_EQ(h1_1.value(2), 1); BOOST_TEST(axis_equal(Type(), h1_1.axis(), h1.axis(1_c))); - auto h2 = make_histogram>(Type(), axis::integer<>(0, 1), - axis::integer<>(0, 2), - axis::integer<>(0, 3)); + auto h2 = make_histogram>(Type(), axis::integer<>(0, 2), + axis::integer<>(0, 3), + axis::integer<>(0, 4)); h2.fill(0, 0, 0); h2.fill(0, 1, 0); h2.fill(0, 1, 1); @@ -623,14 +623,14 @@ template void run_tests() { BOOST_TEST_EQ(h2_0.sum(), 5); BOOST_TEST_EQ(h2_0.value(0), 4); BOOST_TEST_EQ(h2_0.value(1), 1); - BOOST_TEST(axis_equal(Type(), h2_0.axis(), axis::integer<>(0, 1))); + BOOST_TEST(axis_equal(Type(), h2_0.axis(), axis::integer<>(0, 2))); auto h2_1 = reduce(h2, keep(1_c)); BOOST_TEST_EQ(h2_1.dim(), 1); BOOST_TEST_EQ(h2_1.sum(), 5); BOOST_TEST_EQ(h2_1.value(0), 3); BOOST_TEST_EQ(h2_1.value(1), 2); - BOOST_TEST(axis_equal(Type(), h2_1.axis(), axis::integer<>(0, 2))); + BOOST_TEST(axis_equal(Type(), h2_1.axis(), axis::integer<>(0, 3))); auto h2_2 = reduce(h2, keep(2_c)); BOOST_TEST_EQ(h2_2.dim(), 1); @@ -638,7 +638,7 @@ template void run_tests() { BOOST_TEST_EQ(h2_2.value(0), 2); BOOST_TEST_EQ(h2_2.value(1), 1); BOOST_TEST_EQ(h2_2.value(2), 2); - BOOST_TEST(axis_equal(Type(), h2_2.axis(), axis::integer<>(0, 3))); + BOOST_TEST(axis_equal(Type(), h2_2.axis(), axis::integer<>(0, 4))); auto h2_01 = reduce(h2, keep(0_c, 1_c)); BOOST_TEST_EQ(h2_01.dim(), 2); @@ -646,8 +646,8 @@ template void run_tests() { BOOST_TEST_EQ(h2_01.value(0, 0), 2); BOOST_TEST_EQ(h2_01.value(0, 1), 2); BOOST_TEST_EQ(h2_01.value(1, 0), 1); - BOOST_TEST(axis_equal(Type(), h2_01.axis(0_c), axis::integer<>(0, 1))); - BOOST_TEST(axis_equal(Type(), h2_01.axis(1_c), axis::integer<>(0, 2))); + BOOST_TEST(axis_equal(Type(), h2_01.axis(0_c), axis::integer<>(0, 2))); + BOOST_TEST(axis_equal(Type(), h2_01.axis(1_c), axis::integer<>(0, 3))); auto h2_02 = reduce(h2, keep(0_c, 2_c)); BOOST_TEST_EQ(h2_02.dim(), 2); @@ -656,8 +656,8 @@ template void run_tests() { BOOST_TEST_EQ(h2_02.value(0, 1), 1); BOOST_TEST_EQ(h2_02.value(0, 2), 1); BOOST_TEST_EQ(h2_02.value(1, 2), 1); - BOOST_TEST(axis_equal(Type(), h2_02.axis(0_c), axis::integer<>(0, 1))); - BOOST_TEST(axis_equal(Type(), h2_02.axis(1_c), axis::integer<>(0, 3))); + BOOST_TEST(axis_equal(Type(), h2_02.axis(0_c), axis::integer<>(0, 2))); + BOOST_TEST(axis_equal(Type(), h2_02.axis(1_c), axis::integer<>(0, 4))); auto h2_12 = reduce(h2, keep(1_c, 2_c)); BOOST_TEST_EQ(h2_12.dim(), 2); @@ -666,8 +666,8 @@ template void run_tests() { BOOST_TEST_EQ(h2_12.value(1, 0), 1); BOOST_TEST_EQ(h2_12.value(1, 1), 1); BOOST_TEST_EQ(h2_12.value(0, 2), 2); - BOOST_TEST(axis_equal(Type(), h2_12.axis(0_c), axis::integer<>(0, 2))); - BOOST_TEST(axis_equal(Type(), h2_12.axis(1_c), axis::integer<>(0, 3))); + BOOST_TEST(axis_equal(Type(), h2_12.axis(0_c), axis::integer<>(0, 3))); + BOOST_TEST(axis_equal(Type(), h2_12.axis(1_c), axis::integer<>(0, 4))); } } @@ -676,24 +676,24 @@ template void run_mixed_tests() { // compare { auto a = make_histogram>(T1{}, axis::regular<>{3, 0, 3}, - axis::integer<>(0, 1)); + axis::integer<>(0, 2)); auto b = make_histogram>(T2{}, axis::regular<>{3, 0, 3}, - axis::integer<>(0, 1)); + axis::integer<>(0, 2)); BOOST_TEST_EQ(a, b); auto b2 = make_histogram>(T2{}, axis::integer<>{0, 3}, - axis::integer<>(0, 1)); + axis::integer<>(0, 2)); BOOST_TEST_NE(a, b2); auto b3 = make_histogram>(T2{}, axis::regular<>(3, 0, 4), - axis::integer<>(0, 1)); + axis::integer<>(0, 2)); BOOST_TEST_NE(a, b3); } // copy_assign { auto a = make_histogram>(T1{}, axis::regular<>{3, 0, 3}, - axis::integer<>(0, 1)); + axis::integer<>(0, 2)); auto b = make_histogram>(T2{}, axis::regular<>{3, 0, 3}, - axis::integer<>(0, 1)); + axis::integer<>(0, 2)); a.fill(1, 1); BOOST_TEST_NE(a, b); b = a; @@ -713,7 +713,7 @@ int main() { { auto v = std::vector::axis_type>(); v.push_back(axis::regular<>(100, -1, 1)); - v.push_back(axis::integer<>(1, 6)); + v.push_back(axis::integer<>(1, 7)); auto h = histogram(v.begin(), v.end()); BOOST_TEST_EQ(h.axis(0_c), v[0]); BOOST_TEST_EQ(h.axis(1_c), v[1]); @@ -723,16 +723,16 @@ int main() { // utility { - auto c = make_dynamic_histogram(axis::category({"A", "B"})); - BOOST_TEST_THROWS(left(c.axis(), 0), std::runtime_error); - BOOST_TEST_THROWS(right(c.axis(), 0), std::runtime_error); - BOOST_TEST_THROWS(center(c.axis(), 0), std::runtime_error); + enum { A, B }; + auto c = make_dynamic_histogram(axis::category<>({A, B})); + BOOST_TEST_THROWS(bin(c.axis(), 0).lower(), std::runtime_error); + BOOST_TEST_THROWS(bin(c.axis(), 0).upper(), std::runtime_error); } // reduce { auto h1 = - make_dynamic_histogram(axis::integer<>(0, 1), axis::integer<>(0, 2)); + make_dynamic_histogram(axis::integer<>(0, 2), axis::integer<>(0, 3)); h1.fill(0, 0); h1.fill(0, 1); h1.fill(1, 0); diff --git a/test/python_suite_test.py b/test/python_suite_test.py index ef3d11cf..cde0b048 100644 --- a/test/python_suite_test.py +++ b/test/python_suite_test.py @@ -57,7 +57,7 @@ class test_regular(unittest.TestCase): def test_len(self): a = regular(4, 1.0, 2.0) - self.assertEqual(len(a), 5) + self.assertEqual(len(a), 4) def test_repr(self): for s in ("regular(4, 1.1, 2.2)", @@ -69,13 +69,15 @@ class test_regular(unittest.TestCase): def test_getitem(self): v = [1.0, 1.25, 1.5, 1.75, 2.0] a = regular(4, 1.0, 2.0) - for i in range(5): - self.assertEqual(a[i], v[i]) + for i in range(4): + self.assertEqual(a[i].lower, v[i]) + self.assertEqual(a[i].upper, v[i+1]) def test_iter(self): v = [1.0, 1.25, 1.5, 1.75, 2.0] a = regular(4, 1.0, 2.0) - self.assertEqual([x for x in a], v) + self.assertEqual([x.lower for x in a], v[:-1]) + self.assertEqual([x.upper for x in a], v[1:]) def test_index(self): a = regular(4, 1.0, 2.0) @@ -118,8 +120,8 @@ class test_circular(unittest.TestCase): self.assertNotEqual(a, circular(4, 0.0)) def test_len(self): - self.assertEqual(len(circular(4)), 5) - self.assertEqual(len(circular(4, 1.0)), 5) + self.assertEqual(len(circular(4)), 4) + self.assertEqual(len(circular(4, 1.0)), 4) def test_repr(self): for s in ("circular(4)", @@ -131,13 +133,15 @@ class test_circular(unittest.TestCase): def test_getitem(self): v = [1.0, 1.0 + 0.5 * pi, 1.0 + pi, 1.0 + 1.5 *pi, 1.0 + 2.0 * pi] a = circular(4, 1.0) - for i in range(5): - self.assertEqual(a[i], v[i]) + for i in range(4): + self.assertEqual(a[i].lower, v[i]) + self.assertEqual(a[i].upper, v[i+1]) def test_iter(self): a = circular(4, 1.0) v = [1.0, 1.0 + 0.5 * pi, 1.0 + pi, 1.0 + 1.5 *pi, 1.0 + 2.0 * pi] - self.assertEqual([x for x in a], v) + self.assertEqual([x.lower for x in a], v[:-1]) + self.assertEqual([x.upper for x in a], v[1:]) def test_index(self): a = circular(4, 1.0) @@ -180,7 +184,7 @@ class test_variable(unittest.TestCase): self.assertNotEqual(a, variable(-0.1, 0.1)) def test_len(self): - self.assertEqual(len(variable(-0.1, 0.2, 0.3)), 3) + self.assertEqual(len(variable(-0.1, 0.2, 0.3)), 2) def test_repr(self): for s in ("variable(-0.1, 0.2)", @@ -193,13 +197,15 @@ class test_variable(unittest.TestCase): def test_getitem(self): v = [-0.1, 0.2, 0.3] a = variable(*v) - for i in range(3): - self.assertEqual(a[i], v[i]) + for i in range(2): + self.assertEqual(a[i].lower, v[i]) + self.assertEqual(a[i].upper, v[i+1]) def test_iter(self): v = [-0.1, 0.2, 0.3] a = variable(*v) - self.assertEqual([x for x in a], v) + self.assertEqual([x.lower for x in a], v[:-1]) + self.assertEqual([x.upper for x in a], v[1:]) def test_index(self): a = variable(-0.1, 0.2, 0.3) @@ -218,39 +224,39 @@ class test_variable(unittest.TestCase): class test_category(unittest.TestCase): def test_init(self): - category("A", "B", "C") - category("A", "B", "C", label="ca") + category(1, 2, 3) + category(1, 2, 3, label="ca") with self.assertRaises(TypeError): category() with self.assertRaises(TypeError): - category(1) + category("1") with self.assertRaises(TypeError): - category("1", 2) + category(1, "2") with self.assertRaises(TypeError): - category("A", "B", label=1) + category(1, 2, label=1) with self.assertRaises(KeyError): - category("A", "B", "C", uoflow=True) - self.assertEqual(category("A", "B", "C"), - category("A", "B", "C")) + category(1, 2, 3, uoflow=True) + self.assertEqual(category(1, 2, 3), + category(1, 2, 3)) def test_len(self): - a = category("A", "B", "C") + a = category(1, 2, 3) self.assertEqual(len(a), 3) def test_repr(self): - for s in ("category('A')", - "category('A', 'B')", - "category('A', 'B', 'C')"): + for s in ("category(1)", + "category(1, 2)", + "category(1, 2, 3)"): self.assertEqual(str(eval(s)), s) def test_getitem(self): - c = "A", "B", "C" + c = 1, 2, 3 a = category(*c) for i in range(3): self.assertEqual(a[i], c[i]) def test_iter(self): - c = ["A", "B", "C"] + c = [1, 2, 3] self.assertEqual([x for x in category(*c)], c) class test_integer(unittest.TestCase): @@ -273,7 +279,7 @@ class test_integer(unittest.TestCase): integer(-1, 2, uoflow=True)) def test_len(self): - self.assertEqual(len(integer(-1, 2)), 4) + self.assertEqual(len(integer(-1, 3)), 4) def test_repr(self): for s in ("integer(-1, 1)", @@ -287,16 +293,16 @@ class test_integer(unittest.TestCase): def test_getitem(self): v = [-1, 0, 1, 2] - a = integer(-1, 2) + a = integer(-1, 3) for i in range(4): - self.assertEqual(a[i], v[i]) + self.assertEqual(a[i].lower, v[i]) def test_iter(self): - v = [x for x in integer(-1, 2)] + v = [x.lower for x in integer(-1, 3)] self.assertEqual(v, [-1, 0, 1, 2]) def test_index(self): - a = integer(-1, 2) + a = integer(-1, 3) self.assertEqual(a.index(-3), -1) self.assertEqual(a.index(-2), -1) self.assertEqual(a.index(-1), 0) @@ -326,11 +332,11 @@ class test_histogram(unittest.TestCase): with self.assertRaises(RuntimeError): histogram(integer(-1, 1), unknown_keyword="nh") - h = histogram(integer(-1, 1)) + h = histogram(integer(-1, 2)) self.assertEqual(len(h), 1) - self.assertEqual(h.axis(0), integer(-1, 1)) + self.assertEqual(h.axis(0), integer(-1, 2)) self.assertEqual(h.axis(0).shape, 5) - self.assertEqual(histogram(integer(-1, 1, uoflow=False)).axis(0).shape, 3) + self.assertEqual(histogram(integer(-1, 2, uoflow=False)).axis(0).shape, 3) self.assertNotEqual(h, histogram(regular(1, -1, 1))) self.assertNotEqual(h, histogram(integer(-1, 2))) self.assertNotEqual(h, histogram(integer(-1, 1, label="ia"))) @@ -346,8 +352,8 @@ class test_histogram(unittest.TestCase): self.assertNotEqual(id(b), id(c)) def test_fill_1d(self): - h0 = histogram(integer(-1, 1, uoflow=False)) - h1 = histogram(integer(-1, 1, uoflow=True)) + h0 = histogram(integer(-1, 2, uoflow=False)) + h1 = histogram(integer(-1, 2, uoflow=True)) for h in (h0, h1): with self.assertRaises(ValueError): h.fill() @@ -386,7 +392,7 @@ class test_histogram(unittest.TestCase): self.assertEqual(h1.value(3), 1) def test_growth(self): - h = histogram(integer(-1, 1)) + h = histogram(integer(-1, 2)) h.fill(-1) h.fill(1) h.fill(1) @@ -403,7 +409,7 @@ class test_histogram(unittest.TestCase): def test_fill_2d(self): for uoflow in (False, True): - h = histogram(integer(-1, 1, uoflow=uoflow), + h = histogram(integer(-1, 2, uoflow=uoflow), regular(4, -2, 2, uoflow=uoflow)) h.fill(-1, -2) h.fill(-1, -1) @@ -422,13 +428,13 @@ class test_histogram(unittest.TestCase): [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]] - for i in range(-uoflow, h.axis(0).bins + uoflow): - for j in range(-uoflow, h.axis(1).bins + uoflow): + for i in range(-uoflow, len(h.axis(0)) + uoflow): + for j in range(-uoflow, len(h.axis(1)) + uoflow): self.assertEqual(h.value(i, j), m[i][j]) def test_add_2d(self): for uoflow in (False, True): - h = histogram(integer(-1, 1, uoflow=uoflow), + h = histogram(integer(-1, 2, uoflow=uoflow), regular(4, -2, 2, uoflow=uoflow)) h.fill(-1, -2) h.fill(-1, -1) @@ -446,8 +452,8 @@ class test_histogram(unittest.TestCase): h += h - for i in range(-uoflow, h.axis(0).bins + uoflow): - for j in range(-uoflow, h.axis(1).bins + uoflow): + for i in range(-uoflow, len(h.axis(0)) + uoflow): + for j in range(-uoflow, len(h.axis(1)) + uoflow): self.assertEqual(h.value(i, j), 2 * m[i][j]) self.assertEqual(h.variance(i, j), 2 * m[i][j]) @@ -459,7 +465,7 @@ class test_histogram(unittest.TestCase): def test_add_2d_w(self): for uoflow in (False, True): - h = histogram(integer(-1, 1, uoflow=uoflow), + h = histogram(integer(-1, 2, uoflow=uoflow), regular(4, -2, 2, uoflow=uoflow)) h.fill(-1, -2) h.fill(-1, -1) @@ -475,7 +481,7 @@ class test_histogram(unittest.TestCase): [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]] - h2 = histogram(integer(-1, 1, uoflow=uoflow), + h2 = histogram(integer(-1, 2, uoflow=uoflow), regular(4, -2, 2, uoflow=uoflow)) h2.fill(0, 0, weight=0) @@ -484,8 +490,8 @@ class test_histogram(unittest.TestCase): h += h self.assertEqual(h, h2) - for i in range(-uoflow, h.axis(0).bins + uoflow): - for j in range(-uoflow, h.axis(1).bins + uoflow): + for i in range(-uoflow, len(h.axis(0)) + uoflow): + for j in range(-uoflow, len(h.axis(1)) + uoflow): self.assertEqual(h.value(i, j), 2 * m[i][j]) self.assertEqual(h.variance(i, j), 2 * m[i][j]) @@ -533,20 +539,20 @@ class test_histogram(unittest.TestCase): h.variance(4) def test_pickle_0(self): - a = histogram(category('A', 'B', 'C'), + a = histogram(category(0, 1, 2), integer(0, 20, label='ia'), regular(20, 0.0, 20.0, uoflow=False), variable(0.0, 1.0, 2.0), circular(4, label='pa')) - for i in range(a.axis(0).bins): + for i in range(len(a.axis(0))): a.fill(i, 0, 0, 0, 0) - for j in range(a.axis(1).bins): + for j in range(len(a.axis(1))): a.fill(i, j, 0, 0, 0) - for k in range(a.axis(2).bins): + for k in range(len(a.axis(2))): a.fill(i, j, k, 0, 0) - for l in range(a.axis(3).bins): + for l in range(len(a.axis(3))): a.fill(i, j, k, l, 0) - for m in range(a.axis(4).bins): + for m in range(len(a.axis(4))): a.fill(i, j, k, l, m * 0.5 * pi) io = BytesIO() @@ -564,17 +570,17 @@ class test_histogram(unittest.TestCase): self.assertEqual(a, b) def test_pickle_1(self): - a = histogram(category('A', 'B', 'C'), + a = histogram(category(0, 1, 2), integer(0, 3, label='ia'), regular(4, 0.0, 4.0, uoflow=False), variable(0.0, 1.0, 2.0)) - for i in range(a.axis(0).bins): + for i in range(len(a.axis(0))): a.fill(i, 0, 0, 0, weight=3) - for j in range(a.axis(1).bins): + for j in range(len(a.axis(1))): a.fill(i, j, 0, 0, weight=10) - for k in range(a.axis(2).bins): + for k in range(len(a.axis(2))): a.fill(i, j, k, 0, weight=2) - for l in range(a.axis(3).bins): + for l in range(len(a.axis(3))): a.fill(i, j, k, l, weight=5) io = BytesIO() @@ -592,7 +598,7 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_0(self): - a = histogram(integer(0, 2, uoflow=False)) + a = histogram(integer(0, 3, uoflow=False)) for i in range(100): a.fill(1) c = numpy.array(a) # a copy @@ -604,6 +610,7 @@ class test_histogram(unittest.TestCase): for i in range(100): a.fill(1) + # copy does not change, but view does self.assertTrue(numpy.all(c == numpy.array((0, 100, 0)))) self.assertTrue(numpy.all(v == numpy.array((0, 200, 0)))) @@ -617,7 +624,7 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_1(self): - a = histogram(integer(0, 2)) + a = histogram(integer(0, 3)) for i in range(10): a.fill(1, weight=3) c = numpy.array(a) # a copy @@ -628,13 +635,13 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_2(self): - a = histogram(integer(0, 1, uoflow=False), - integer(0, 2, uoflow=False), - integer(0, 3, uoflow=False)) + a = histogram(integer(0, 2, uoflow=False), + integer(0, 3, uoflow=False), + integer(0, 4, uoflow=False)) r = numpy.zeros((2, 3, 4), dtype=numpy.int8) - for i in range(a.axis(0).bins): - for j in range(a.axis(1).bins): - for k in range(a.axis(2).bins): + for i in range(len(a.axis(0))): + for j in range(len(a.axis(1))): + for k in range(len(a.axis(2))): for m in range(i+j+k): a.fill(i, j, k) r[i,j,k] = i+j+k @@ -642,9 +649,9 @@ class test_histogram(unittest.TestCase): v = numpy.asarray(a) # a view c2 = numpy.zeros((2, 3, 4), dtype=numpy.int8) - for i in range(a.axis(0).bins): - for j in range(a.axis(1).bins): - for k in range(a.axis(2).bins): + for i in range(len(a.axis(0))): + for j in range(len(a.axis(1))): + for k in range(len(a.axis(2))): c2[i,j,k] = a.value(i,j,k) self.assertTrue(numpy.all(c == c2)) @@ -653,13 +660,13 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_3(self): - a = histogram(integer(0, 1), - integer(0, 2), - integer(0, 3)) + a = histogram(integer(0, 2), + integer(0, 3), + integer(0, 4)) r = numpy.zeros((2, 4, 5, 6)) - for i in range(a.axis(0).bins): - for j in range(a.axis(1).bins): - for k in range(a.axis(2).bins): + for i in range(len(a.axis(0))): + for j in range(len(a.axis(1))): + for k in range(len(a.axis(2))): a.fill(i, j, k, weight=i+j+k) r[0, i, j, k] = i+j+k r[1, i, j, k] = (i+j+k)**2 @@ -667,9 +674,9 @@ class test_histogram(unittest.TestCase): v = numpy.asarray(a) # a view c2 = numpy.zeros((2, 4, 5, 6)) - for i in range(a.axis(0).bins): - for j in range(a.axis(1).bins): - for k in range(a.axis(2).bins): + for i in range(len(a.axis(0))): + for j in range(len(a.axis(1))): + for k in range(len(a.axis(2))): c2[0, i, j, k] = a.value(i,j,k) c2[1, i, j, k] = a.variance(i,j,k) @@ -679,8 +686,8 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_4(self): - a = histogram(integer(0, 1, uoflow=False), - integer(0, 3, uoflow=False)) + a = histogram(integer(0, 2, uoflow=False), + integer(0, 4, uoflow=False)) a1 = numpy.asarray(a) self.assertEqual(a1.dtype, numpy.uint8) self.assertEqual(a1.shape, (2, 4)) @@ -692,7 +699,7 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_5(self): - a = histogram(integer(0, 1, uoflow=False)) + a = histogram(integer(0, 2, uoflow=False)) a.fill(0) for i in xrange(80): a += a @@ -702,10 +709,26 @@ class test_histogram(unittest.TestCase): self.assertEqual(a1[0], float(2 ** 80)) self.assertEqual(a1[1], 0) + @unittest.skipUnless(have_numpy, "requires build with numpy-support") + def test_numpy_conversion_6(self): + a = integer(0, 2) + b = regular(2, 0, 2) + c = variable(0, 1, 2) + ref = numpy.array((0., 1., 2.)) + self.assertTrue(numpy.all(numpy.array(a) == ref)) + self.assertTrue(numpy.all(numpy.array(b) == ref)) + self.assertTrue(numpy.all(numpy.array(c) == ref)) + d = circular(4) + ref = numpy.array((0., 0.5*pi, pi, 1.5*pi, 2.0*pi)) + self.assertTrue(numpy.all(numpy.array(d) == ref)) + e = category(1, 2) + ref = numpy.array((1, 2)) + self.assertTrue(numpy.all(numpy.array(e) == ref)) + @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_fill_with_numpy_array_0(self): ar = lambda *args: numpy.array(args) - a = histogram(integer(0, 2, uoflow=False)) + a = histogram(integer(0, 3, uoflow=False)) a.fill(ar(-1, 0, 1, 2, 1, 4)) a.fill((-1, 0)) a.fill([1, 2]) @@ -720,7 +743,7 @@ class test_histogram(unittest.TestCase): with self.assertRaises(ValueError): a.fill("abc") - a = histogram(integer(0, 1, uoflow=False), + a = histogram(integer(0, 2, uoflow=False), regular(2, 0, 2, uoflow=False)) a.fill(ar(-1, 0, 1), ar(-1., 1., 0.1)) @@ -732,7 +755,7 @@ class test_histogram(unittest.TestCase): with self.assertRaises(ValueError): a.fill((1, 2, 3)) - a = histogram(integer(0, 2, uoflow=False)) + a = histogram(integer(0, 3, uoflow=False)) a.fill([0, 0, 1, 2]) a.fill((1, 0, 2, 2)) self.assertEqual(a.value(0), 3) @@ -743,7 +766,7 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_fill_with_numpy_array_1(self): ar = lambda *args: numpy.array(args) - a = histogram(integer(0, 2, uoflow=True)) + a = histogram(integer(0, 3, uoflow=True)) v = numpy.array([-1, 0, 1, 2, 3, 4]) w = numpy.array([ 2, 3, 4, 5, 6, 7]) a.fill(v, weight=w) @@ -773,14 +796,14 @@ class test_histogram(unittest.TestCase): with self.assertRaises(ValueError): a.fill([1, 2], weight=[[1, 1], [2, 2]]) - a = histogram(integer(0, 1, uoflow=False), + a = histogram(integer(0, 2, uoflow=False), regular(2, 0, 2, uoflow=False)) a.fill(ar(-1, 0, 1), ar(-1, 1, 0.1)) self.assertEqual(a.value(0, 0), 0) self.assertEqual(a.value(0, 1), 1) self.assertEqual(a.value(1, 0), 1) self.assertEqual(a.value(1, 1), 0) - a = histogram(integer(0, 2, uoflow=False)) + a = histogram(integer(0, 3, uoflow=False)) a.fill([0, 0, 1, 2]) a.fill((1, 0, 2, 2)) self.assertEqual(a.value(0), 3)