diff --git a/doc/changelog.qbk b/doc/changelog.qbk index 2478f1f5..94b5dec5 100644 --- a/doc/changelog.qbk +++ b/doc/changelog.qbk @@ -20,7 +20,7 @@ * Added new `accumulators::fraction` to compute fractions, their variance, and confidence intervals * Added interval computers for fractions: `utility::clopper_pearson`, `utility::wilson_interval`, `utility::jeffreys_interval`, `utility::wald_interval` which can compute intervals with arbitrary confidence level * Added `utility::confidence_level` and `utility::deviation` types to pass confidence levels as probabilities or in multiples of standard deviation for all interval computers, respectively -* Fixed internal `sub_array` and `span` in C++20 +* Fixed internal `static_vector` and `span` in C++20 [heading Boost 1.80] diff --git a/doc/concepts/Accumulator.qbk b/doc/concepts/Accumulator.qbk index 12266603..be89b42a 100644 --- a/doc/concepts/Accumulator.qbk +++ b/doc/concepts/Accumulator.qbk @@ -92,5 +92,6 @@ An [*Accumulator] is a functor which consumes the argument to update some intern * [classref boost::histogram::accumulators::weighted_sum] * [classref boost::histogram::accumulators::mean] * [classref boost::histogram::accumulators::weighted_mean] +* [classref boost::histogram::accumulators::collector] [endsect] diff --git a/doc/guide.qbk b/doc/guide.qbk index f9486e9e..d91c5063 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -449,6 +449,7 @@ The library provides several accumulators: * [classref boost::histogram::accumulators::weighted_mean weighted_mean] accepts a sample and a weight. It computes the weighted mean of the samples. [funcref boost::histogram::make_weighted_profile make_weighted_profile] uses this accumulator. * [classref boost::histogram::accumulators::fraction fraction] accepts a boolean sample that represents success or failure of a binomial trial. It computes the fraction of successes. One can access the number of successes and failures, the fraction, the estimated variance of the fraction, and a confidence interval. The standard confidence interval is the Wilson score interval, but more interval computers are implemented in `boost/histogram/utility`. Beware: one cannot pass `std::vector` to [classref boost::histogram::histogram histogram::fill], because it is not a contiguous sequence of boolean values, but any other container of booleans works and any sequence of values convertible to bool. +* [classref boost::histogram::accumulators::collector collector] consists of a collection of containers, one per bin. It accepts samples and sorts the sample value into the corresponding container. The memory consumption of this accumulator is unbounded, since it stores each input value. It is useful to compute custom estimators, in particular, those which require access to the full sample, like a kernel density estimate, or which do not have online update algorithms (for example, the median). Users can easily write their own accumulators and plug them into the histogram, if they adhere to the [link histogram.concepts.Accumulator [*Accumulator] concept]. All accumulators from [@boost:/libs/accumulators/index.html Boost.Accumulators] that accept a single argument and no weights work out of the box. Other accumulators from Boost.Accumulators can be made to work by using them inside a wrapper class that implements the concept. diff --git a/include/boost/histogram/accumulators.hpp b/include/boost/histogram/accumulators.hpp index af449d32..b2826711 100644 --- a/include/boost/histogram/accumulators.hpp +++ b/include/boost/histogram/accumulators.hpp @@ -17,6 +17,7 @@ [1]: histogram/reference.html#header.boost.histogram.accumulators.ostream_hpp */ +#include #include #include #include diff --git a/include/boost/histogram/accumulators/collector.hpp b/include/boost/histogram/accumulators/collector.hpp new file mode 100644 index 00000000..b800732e --- /dev/null +++ b/include/boost/histogram/accumulators/collector.hpp @@ -0,0 +1,110 @@ +// Copyright 2024 Ruggero Turra, Hans Dembinski +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_ACCUMULATORS_COLLECTOR_HPP +#define BOOST_HISTOGRAM_ACCUMULATORS_COLLECTOR_HPP + +#include // for std::equal +#include +#include +#include // for collector<> +#include +#include + +namespace boost { +namespace histogram { +namespace accumulators { + +/** Collects samples. + + Input samples are stored in an internal container for later retrival, which stores the + values consecutively in memory. The interface is designed to work with std::vector and + other containers which implement the same API. + + Warning: The memory of the accumulator is unbounded. +*/ +template +class collector { +public: + using container_type = ContainerType; + using value_type = typename container_type::value_type; + using allocator_type = typename container_type::allocator_type; + using const_reference = typename container_type::const_reference; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using size_type = typename container_type::size_type; + using const_pointer = typename container_type::const_pointer; + + // make template only match if forwarding args to container is valid + template ()...))> + explicit collector(Args&&... args) : container_(std::forward(args)...) {} + + // make template only match if forwarding args to container is valid + template (),std::declval()...))> + explicit collector(std::initializer_list list, Args&&... args) + : container_(list, std::forward(args)...) {} + + /// Append sample x. + void operator()(const_reference x) { container_.push_back(x); } + + /// Append samples from another collector. + template + collector& operator+=(const collector& rhs) { + container_.reserve(size() + rhs.size()); + container_.insert(end(), rhs.begin(), rhs.end()); + return *this; + } + + /// Return true if collections are equal. + /// + /// Two collections are equal if they have the same number of elements + /// which all compare equal. + template > + bool operator==(const Iterable& rhs) const noexcept { + return std::equal(begin(), end(), rhs.begin(), rhs.end()); + } + + /// Return true if collections are not equal. + template > + bool operator!=(const Iterable& rhs) const noexcept { + return !operator==(rhs); + } + + /// Return number of samples. + size_type size() const noexcept { return container_.size(); } + + /// Return number of samples (alias for size()). + size_type count() const noexcept { return container_.size(); } + + /// Return readonly iterator to start of collection. + const const_iterator begin() const noexcept { return container_.begin(); } + + /// Return readonly iterator to end of collection. + const const_iterator end() const noexcept { return container_.end(); } + + /// Return const reference to value at index. + const_reference operator[](size_type idx) const noexcept { return container_[idx]; } + + /// Return pointer to internal memory. + const_pointer data() const noexcept { return container_.data(); } + + allocator_type get_allocator() const { return container_.get_allocator(); } + + template + void serialize(Archive& ar, unsigned version) { + (void)version; + ar& make_nvp("container", container_); + } + +private: + container_type container_; +}; + +} // namespace accumulators +} // namespace histogram +} // namespace boost + +#endif \ No newline at end of file diff --git a/include/boost/histogram/accumulators/ostream.hpp b/include/boost/histogram/accumulators/ostream.hpp index 46b914c9..b9be96b6 100644 --- a/include/boost/histogram/accumulators/ostream.hpp +++ b/include/boost/histogram/accumulators/ostream.hpp @@ -101,6 +101,20 @@ std::basic_ostream& operator<<(std::basic_ostream& return detail::handle_nonzero_width(os, x); } +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const collector& x) { + if (os.width() == 0) { + os << "collector{"; + auto iter = x.begin(); + if (iter != x.end()) os << *iter++; + for (; iter != x.end(); ++iter) os << ", " << *iter; + os << "}"; + return os; + } + return detail::handle_nonzero_width(os, x); +} + } // namespace accumulators } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/detail/axes.hpp b/include/boost/histogram/detail/axes.hpp index 44b7486b..7331e79c 100644 --- a/include/boost/histogram/detail/axes.hpp +++ b/include/boost/histogram/detail/axes.hpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -381,13 +381,13 @@ std::size_t offset(const T& axes) { // make default-constructed buffer (no initialization for POD types) template auto make_stack_buffer(const A& a) { - return sub_array::value>(axes_rank(a)); + return static_vector::value>(axes_rank(a)); } // make buffer with elements initialized to v template auto make_stack_buffer(const A& a, const T& t) { - return sub_array::value>(axes_rank(a), t); + return static_vector::value>(axes_rank(a), t); } template diff --git a/include/boost/histogram/detail/chunk_vector.hpp b/include/boost/histogram/detail/chunk_vector.hpp new file mode 100644 index 00000000..18fa8b51 --- /dev/null +++ b/include/boost/histogram/detail/chunk_vector.hpp @@ -0,0 +1,103 @@ +// Copyright 2019 Hans Dembinski +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_HISTOGRAM_DETAIL_CHUNK_VECTOR_HPP +#define BOOST_HISTOGRAM_DETAIL_CHUNK_VECTOR_HPP + +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace detail { + +// Warning: this is not a proper container and is only used to +// test the feasibility of using accumulators::collector with a +// custom container type. If time permits, this will be expanded +// into a proper container type. +template +class chunk_vector { +public: + using base = std::vector; + using allocator_type = typename base::allocator_type; + using pointer = typename base::pointer; + using const_pointer = typename base::const_pointer; + using size_type = typename base::size_type; + using const_reference = boost::span; + using reference = boost::span; + // this is wrong and should make a copy; it is not a problem for + // the current use-case, but a general purpose implementation cannot + // violate concepts like this + using value_type = const_reference; + + template + struct iterator_t { + iterator_t& operator++() { + ptr_ += chunk_; + return *this; + } + + iterator_t operator++(int) { + iterator_t copy(*this); + ptr_ += chunk_; + return copy; + } + + value_type operator*() const { return value_type(ptr_, ptr_ + chunk_); } + + Pointer ptr_; + size_type chunk_; + }; + + using iterator = iterator_t; + using const_iterator = iterator_t; + + // this creates an empty chunk_vector + explicit chunk_vector(size_type chunk, const allocator_type& alloc = {}) + : chunk_(chunk), vec_(alloc) {} + + chunk_vector(std::initializer_list list, size_type chunk, + const allocator_type& alloc = {}) + : chunk_(chunk), vec_(list, alloc) {} + + allocator_type get_allocator() noexcept(noexcept(allocator_type())) { + return vec_.get_allocator(); + } + + void push_back(const_reference x) { + if (x.size() != chunk_) + BOOST_THROW_EXCEPTION(std::runtime_error("argument has wrong size")); + // we don't use std::vector::insert here to have amortized constant complexity + for (auto&& elem : x) vec_.push_back(elem); + } + + auto insert(const_iterator pos, const_iterator o_begin, const_iterator o_end) { + if (std::distance(o_begin, o_end) % chunk_ == 0) + BOOST_THROW_EXCEPTION(std::runtime_error("argument has wrong size")); + return vec_.insert(pos, o_begin, o_end); + } + + const_iterator begin() const noexcept { return {vec_.data(), chunk_}; } + const_iterator end() const noexcept { return {vec_.data() + vec_.size(), chunk_}; } + + value_type operator[](size_type idx) const noexcept { + return {vec_.data() + idx * chunk_, vec_.data() + (idx + 1) * chunk_}; + } + + size_type size() const noexcept { return vec_.size() / chunk_; } + +private: + size_type chunk_; + base vec_; +}; + +} // namespace detail +} // namespace histogram +} // namespace boost + +#endif \ No newline at end of file diff --git a/include/boost/histogram/detail/detect.hpp b/include/boost/histogram/detail/detect.hpp index 68cc9b70..d62ab4dd 100644 --- a/include/boost/histogram/detail/detect.hpp +++ b/include/boost/histogram/detail/detect.hpp @@ -66,6 +66,8 @@ struct detect_base { // reset has overloads, trying to get pmf in this case always fails BOOST_HISTOGRAM_DETAIL_DETECT(has_method_reset, t.reset(0)); +BOOST_HISTOGRAM_DETAIL_DETECT(has_method_push_back, (&T::push_back)); + BOOST_HISTOGRAM_DETAIL_DETECT(is_indexable, t[0]); BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(is_transform, (t.inverse(t.forward(u)))); diff --git a/include/boost/histogram/detail/sub_array.hpp b/include/boost/histogram/detail/static_vector.hpp similarity index 75% rename from include/boost/histogram/detail/sub_array.hpp rename to include/boost/histogram/detail/static_vector.hpp index d60af2b9..ca2ea504 100644 --- a/include/boost/histogram/detail/sub_array.hpp +++ b/include/boost/histogram/detail/static_vector.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_HISTOGRAM_DETAIL_SUB_ARRAY_HPP -#define BOOST_HISTOGRAM_DETAIL_SUB_ARRAY_HPP +#ifndef BOOST_HISTOGRAM_DETAIL_STATIC_VECTOR_HPP +#define BOOST_HISTOGRAM_DETAIL_STATIC_VECTOR_HPP #include #include @@ -15,10 +15,11 @@ namespace boost { namespace histogram { namespace detail { -// Like std::array, but allows to use less than maximum capacity. -// Cannot inherit from std::array, since this confuses span. +// A crude implementation of boost::container::static_vector. +// Like std::vector, but with static allocation up to a maximum capacity. template -class sub_array { +class static_vector { + // Cannot inherit from std::array, since this confuses span. static constexpr bool swap_element_is_noexcept() noexcept { using std::swap; return noexcept(swap(std::declval(), std::declval())); @@ -34,19 +35,19 @@ public: using iterator = pointer; using const_iterator = const_pointer; - sub_array() = default; + static_vector() = default; - explicit sub_array(std::size_t s) noexcept : size_(s) { assert(size_ <= N); } + explicit static_vector(std::size_t s) noexcept : size_(s) { assert(size_ <= N); } - sub_array(std::size_t s, const T& value) noexcept( + static_vector(std::size_t s, const T& value) noexcept( std::is_nothrow_assignable::value) - : sub_array(s) { + : static_vector(s) { fill(value); } - sub_array(std::initializer_list il) noexcept( + static_vector(std::initializer_list il) noexcept( std::is_nothrow_assignable::value) - : sub_array(il.size()) { + : static_vector(il.size()) { std::copy(il.begin(), il.end(), data_); } @@ -90,7 +91,7 @@ public: std::fill(begin(), end(), value); } - void swap(sub_array& other) noexcept(swap_element_is_noexcept()) { + void swap(static_vector& other) noexcept(swap_element_is_noexcept()) { using std::swap; const size_type s = (std::max)(size(), other.size()); for (auto i = begin(), j = other.begin(), end = begin() + s; i != end; ++i, ++j) @@ -104,12 +105,12 @@ private: }; template -bool operator==(const sub_array& a, const sub_array& b) noexcept { +bool operator==(const static_vector& a, const static_vector& b) noexcept { return std::equal(a.begin(), a.end(), b.begin(), b.end()); } template -bool operator!=(const sub_array& a, const sub_array& b) noexcept { +bool operator!=(const static_vector& a, const static_vector& b) noexcept { return !(a == b); } @@ -119,8 +120,9 @@ bool operator!=(const sub_array& a, const sub_array& b) noexcept { namespace std { template -void swap(::boost::histogram::detail::sub_array& a, - ::boost::histogram::detail::sub_array& b) noexcept(noexcept(a.swap(b))) { +void swap( + ::boost::histogram::detail::static_vector& a, + ::boost::histogram::detail::static_vector& b) noexcept(noexcept(a.swap(b))) { a.swap(b); } } // namespace std diff --git a/include/boost/histogram/fwd.hpp b/include/boost/histogram/fwd.hpp index 3c48f784..9e54f256 100644 --- a/include/boost/histogram/fwd.hpp +++ b/include/boost/histogram/fwd.hpp @@ -109,6 +109,9 @@ class mean; template class weighted_mean; +template > +class collector; + } // namespace accumulators struct unsafe_access; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 687f6abd..7823d897 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -47,6 +47,7 @@ boost_test(TYPE run SOURCES accumulators_mean_test.cpp) boost_test(TYPE run SOURCES accumulators_sum_test.cpp) boost_test(TYPE run SOURCES accumulators_weighted_mean_test.cpp) boost_test(TYPE run SOURCES accumulators_weighted_sum_test.cpp) +boost_test(TYPE run SOURCES accumulators_collector_test.cpp) boost_test(TYPE run SOURCES algorithm_project_test.cpp) boost_test(TYPE run SOURCES algorithm_reduce_test.cpp) boost_test(TYPE run SOURCES algorithm_sum_test.cpp) @@ -75,7 +76,7 @@ boost_test(TYPE run SOURCES detail_operators_test.cpp) boost_test(TYPE run SOURCES detail_relaxed_equal_test.cpp) boost_test(TYPE run SOURCES detail_replace_type_test.cpp) boost_test(TYPE run SOURCES detail_safe_comparison_test.cpp) -boost_test(TYPE run SOURCES detail_sub_array_and_span_test.cpp) +boost_test(TYPE run SOURCES detail_static_vector_and_span_test.cpp) boost_test(TYPE run SOURCES detail_static_if_test.cpp) boost_test(TYPE run SOURCES detail_tuple_slice_test.cpp) boost_test(TYPE run SOURCES histogram_custom_axis_test.cpp) @@ -91,6 +92,7 @@ boost_test(TYPE run SOURCES histogram_ostream_ascii_test.cpp) set_tests_properties(run-boost_histogram-histogram_ostream_ascii_test PROPERTIES ENVIRONMENT "LANG=FOO;COLUMNS=20") boost_test(TYPE run SOURCES histogram_fraction_test.cpp) +boost_test(TYPE run SOURCES histogram_collector_test.cpp) boost_test(TYPE run SOURCES histogram_test.cpp) boost_test(TYPE run SOURCES indexed_test.cpp) boost_test(TYPE run SOURCES storage_adaptor_test.cpp) diff --git a/test/Jamfile b/test/Jamfile index 3df93a9f..6cebf317 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -49,6 +49,7 @@ alias cxx14 : clang:"-O3 -ffast-math" ] [ run accumulators_weighted_mean_test.cpp ] [ run accumulators_weighted_sum_test.cpp ] + [ run accumulators_collector_test.cpp ] [ run algorithm_project_test.cpp ] [ run algorithm_reduce_test.cpp ] [ run algorithm_sum_test.cpp ] @@ -78,7 +79,7 @@ alias cxx14 : [ run detail_replace_type_test.cpp ] [ run detail_safe_comparison_test.cpp ] [ run detail_static_if_test.cpp ] - [ run detail_sub_array_and_span_test.cpp ] + [ run detail_static_vector_and_span_test.cpp ] [ run detail_tuple_slice_test.cpp ] [ run histogram_custom_axis_test.cpp ] [ run histogram_dynamic_test.cpp ] @@ -87,6 +88,7 @@ alias cxx14 : [ run histogram_mixed_test.cpp ] [ run histogram_operators_test.cpp ] [ run histogram_fraction_test.cpp ] + [ run histogram_collector_test.cpp ] [ run histogram_test.cpp ] [ run indexed_test.cpp ] [ run storage_adaptor_test.cpp ] diff --git a/test/accumulators_collector_test.cpp b/test/accumulators_collector_test.cpp new file mode 100644 index 00000000..3207614a --- /dev/null +++ b/test/accumulators_collector_test.cpp @@ -0,0 +1,139 @@ +// Copyright 2024 Ruggero Turra, Hans Dembinski +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ostream.hpp" +#include "str.hpp" +#include "throw_exception.hpp" + +using namespace boost::histogram; + +namespace boost { +template +bool operator==(const span& a, const std::vector& b) { + return std::equal(a.begin(), a.end(), b.begin(), b.end()); +} +} // namespace boost + +int main() { + using traits = detail::accumulator_traits>; + static_assert(!traits::weight_support, ""); + static_assert(std::is_same>::value, ""); + + { + accumulators::collector> acc; + BOOST_TEST_EQ(acc.count(), 0); + BOOST_TEST_EQ(acc.size(), 0); + BOOST_TEST_EQ(str(acc), "collector{}"); + acc(1); + BOOST_TEST_EQ(str(acc), "collector{1}"); + acc(2); + BOOST_TEST_EQ(str(acc), "collector{1, 2}"); + BOOST_TEST_EQ(acc.count(), 2); + BOOST_TEST_EQ(acc.size(), 2); + + const std::vector ref = {{1, 2}}; + BOOST_TEST_ALL_EQ(acc.begin(), acc.end(), ref.begin(), ref.end()); + } + + { + accumulators::collector> acc{{1, 2}}; + accumulators::collector> copy(acc); + BOOST_TEST_ALL_EQ(copy.begin(), copy.end(), acc.begin(), acc.end()); + copy(3); + BOOST_TEST_EQ(copy.size(), 3); + BOOST_TEST_NE(acc, copy); + } + + { + accumulators::collector> acc1; + acc1(1); + acc1(2); + + accumulators::collector> acc2; + BOOST_TEST_NE(acc1, acc2); // acc2 is empty + acc2(2); + acc2(1); + BOOST_TEST_EQ(str(acc2), "collector{2, 1}"); + BOOST_TEST_NE(acc1, acc2); // order matters + + accumulators::collector> acc3; + acc3(1); + acc3(2); + BOOST_TEST_EQ(acc1, acc3); + + // comparison to another embedded container type + accumulators::collector> acc4; + acc4(1); + acc4(2); + BOOST_TEST_EQ(acc1, acc4); + + // comparison to another container type + std::vector arr = {{1.0, 2.0}}; + BOOST_TEST_EQ(acc1, arr); + } + + { + BOOST_TEST_EQ(accumulators::collector<>{} += accumulators::collector<>{}, + accumulators::collector<>{}); + + accumulators::collector<> acc1; + acc1(1); + acc1(2); + accumulators::collector<> acc2; + acc1 += acc2; // acc1 = [1, 2] acc2 = [] + BOOST_TEST_EQ(acc1.count(), 2); + acc2(3); // acc2 = [3] + acc1 += acc2; // acc = [1, 2, 3] acc2 = [3] + BOOST_TEST_EQ(acc1.count(), 3); + BOOST_TEST_EQ(str(acc1), "collector{1, 2, 3}"); + acc1 += accumulators::collector>{{4.0, 5.0}}; + BOOST_TEST_EQ(acc1.count(), 5); + BOOST_TEST_EQ(str(acc1), "collector{1, 2, 3, 4, 5}"); + } + + { + using A = std::array; + accumulators::collector> acc; + acc(A{1, 2}); + BOOST_TEST_EQ(acc.size(), 1); + BOOST_TEST_EQ(str(acc), "collector{[ 1 2 ]}"); + acc(A{3, 4}); + BOOST_TEST_EQ(str(acc), "collector{[ 1 2 ], [ 3 4 ]}"); + BOOST_TEST_EQ(acc, (std::vector{A{1, 2}, A{3, 4}})); + } + + { + accumulators::collector> acc(2); + + std::vector x = {{1, 2}}; + acc(x); + + BOOST_TEST_EQ(acc.size(), 1); + BOOST_TEST_EQ(acc[0], x); + + x = {{3, 4}}; + acc(x); + + BOOST_TEST_EQ(acc.size(), 2); + std::vector x2 = {{1, 2}}; + BOOST_TEST_EQ(acc[0], x2); + BOOST_TEST_EQ(acc[1], x); + BOOST_TEST_EQ(acc[1][0], 3); + BOOST_TEST_EQ(acc[1][1], 4); + } + + return boost::report_errors(); +} diff --git a/test/accumulators_mean_test.cpp b/test/accumulators_mean_test.cpp index 3509527c..bf737803 100644 --- a/test/accumulators_mean_test.cpp +++ b/test/accumulators_mean_test.cpp @@ -7,8 +7,11 @@ #include #include #include +#include #include #include +#include +#include #include "is_close.hpp" #include "str.hpp" #include "throw_exception.hpp" @@ -19,6 +22,10 @@ using namespace std::literals; int main() { using m_t = accumulators::mean; + using traits = detail::accumulator_traits; + static_assert(traits::weight_support, ""); + static_assert(std::is_same>::value, ""); + // basic interface, string conversion { m_t a; diff --git a/test/accumulators_serialization_test.cpp b/test/accumulators_serialization_test.cpp index b09132a8..bc9d015e 100644 --- a/test/accumulators_serialization_test.cpp +++ b/test/accumulators_serialization_test.cpp @@ -102,5 +102,19 @@ int main(int argc, char** argv) { BOOST_TEST(a == b); } + // collector + { + const auto filename = join(argv[1], "accumulators_serialization_test_collector.xml"); + accumulators::collector<> a; + a(1.5); + a(4.0); + print_xml(filename, a); + + accumulators::collector<> b; + BOOST_TEST_NOT(a == b); + load_xml(filename, b); + BOOST_TEST(a == b); + } + return boost::report_errors(); } diff --git a/test/accumulators_serialization_test_collector.xml b/test/accumulators_serialization_test_collector.xml new file mode 100644 index 00000000..8107ac33 --- /dev/null +++ b/test/accumulators_serialization_test_collector.xml @@ -0,0 +1,20 @@ + + + + + + + + 2 + 0 + 1.50000000000000000e+00 + 4.00000000000000000e+00 + + + \ No newline at end of file diff --git a/test/axis_boolean_test.cpp b/test/axis_boolean_test.cpp index 280415cf..95f118af 100644 --- a/test/axis_boolean_test.cpp +++ b/test/axis_boolean_test.cpp @@ -11,7 +11,7 @@ #include #include #include "axis.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "str.hpp" #include "throw_exception.hpp" diff --git a/test/axis_category_test.cpp b/test/axis_category_test.cpp index d485e56a..ae97240d 100644 --- a/test/axis_category_test.cpp +++ b/test/axis_category_test.cpp @@ -14,7 +14,7 @@ #include #include #include "axis.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "str.hpp" #include "throw_exception.hpp" diff --git a/test/axis_integer_test.cpp b/test/axis_integer_test.cpp index 2517d9cd..8a853481 100644 --- a/test/axis_integer_test.cpp +++ b/test/axis_integer_test.cpp @@ -11,7 +11,7 @@ #include #include #include "axis.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "str.hpp" #include "throw_exception.hpp" diff --git a/test/axis_regular_test.cpp b/test/axis_regular_test.cpp index 0e7cb0d6..6b7c0b5d 100644 --- a/test/axis_regular_test.cpp +++ b/test/axis_regular_test.cpp @@ -12,7 +12,7 @@ #include #include "axis.hpp" #include "is_close.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "str.hpp" #include "throw_exception.hpp" diff --git a/test/axis_traits_test.cpp b/test/axis_traits_test.cpp index 78fed9ca..43d344d7 100644 --- a/test/axis_traits_test.cpp +++ b/test/axis_traits_test.cpp @@ -9,7 +9,7 @@ #include #include #include "axis.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram::axis; diff --git a/test/axis_variable_test.cpp b/test/axis_variable_test.cpp index 5eabd621..1216bb77 100644 --- a/test/axis_variable_test.cpp +++ b/test/axis_variable_test.cpp @@ -13,7 +13,7 @@ #include #include "axis.hpp" #include "is_close.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "str.hpp" #include "throw_exception.hpp" diff --git a/test/axis_variant_test.cpp b/test/axis_variant_test.cpp index c1419ac9..d7fb20e2 100644 --- a/test/axis_variant_test.cpp +++ b/test/axis_variant_test.cpp @@ -14,7 +14,7 @@ #include #include "allocator.hpp" #include "axis.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "str.hpp" #include "throw_exception.hpp" diff --git a/test/deduction_guides_test.cpp b/test/deduction_guides_test.cpp index 9aca4f9c..6a946d41 100644 --- a/test/deduction_guides_test.cpp +++ b/test/deduction_guides_test.cpp @@ -16,7 +16,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/detail_args_type_test.cpp b/test/detail_args_type_test.cpp index d9e4b242..319807b0 100644 --- a/test/detail_args_type_test.cpp +++ b/test/detail_args_type_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" namespace dtl = boost::histogram::detail; diff --git a/test/detail_array_wrapper_serialization_test.cpp b/test/detail_array_wrapper_serialization_test.cpp index ec336d56..1446fa65 100644 --- a/test/detail_array_wrapper_serialization_test.cpp +++ b/test/detail_array_wrapper_serialization_test.cpp @@ -13,7 +13,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" namespace dtl = boost::histogram::detail; @@ -25,7 +25,7 @@ struct dummy_array_wrapper { std::size_t size; template void serialize(Archive& ar, unsigned /* version */) { - for (auto&& x : boost::make_span(ptr, size)) ar& x; + for (auto&& x : boost::make_span(ptr, size)) ar & x; } }; diff --git a/test/detail_axes_test.cpp b/test/detail_axes_test.cpp index cbdbd91a..9e08cadf 100644 --- a/test/detail_axes_test.cpp +++ b/test/detail_axes_test.cpp @@ -14,7 +14,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/detail_convert_integer_test.cpp b/test/detail_convert_integer_test.cpp index 10e5f105..a098f2ba 100644 --- a/test/detail_convert_integer_test.cpp +++ b/test/detail_convert_integer_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" using namespace boost::histogram::detail; diff --git a/test/detail_detect_test.cpp b/test/detail_detect_test.cpp index 8ce27b6d..86380acc 100644 --- a/test/detail_detect_test.cpp +++ b/test/detail_detect_test.cpp @@ -22,7 +22,7 @@ #include #include #include "allocator.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/detail_iterator_adaptor_test.cpp b/test/detail_iterator_adaptor_test.cpp index 84352ae2..5d332944 100644 --- a/test/detail_iterator_adaptor_test.cpp +++ b/test/detail_iterator_adaptor_test.cpp @@ -15,7 +15,7 @@ #include #include #include "iterator.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" using boost::histogram::detail::iterator_adaptor; diff --git a/test/detail_large_int_test.cpp b/test/detail_large_int_test.cpp index 7e50fcef..7e24b95c 100644 --- a/test/detail_large_int_test.cpp +++ b/test/detail_large_int_test.cpp @@ -9,7 +9,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" namespace boost { namespace histogram { diff --git a/test/detail_misc_test.cpp b/test/detail_misc_test.cpp index cf36e87f..f1294c8d 100644 --- a/test/detail_misc_test.cpp +++ b/test/detail_misc_test.cpp @@ -14,14 +14,14 @@ #include #include #include -#include +#include #include #include #include #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" namespace boost { @@ -126,9 +126,9 @@ int main() { BOOST_TEST_EQ(count, 6); } - // sub_array and make_span + // static_vector and make_span { - dtl::sub_array a(2, 1); + dtl::static_vector a(2, 1); a[1] = 2; auto sp = boost::make_span(a); BOOST_TEST_EQ(sp.size(), 2); diff --git a/test/detail_relaxed_equal_test.cpp b/test/detail_relaxed_equal_test.cpp index 67864d1e..231222d0 100644 --- a/test/detail_relaxed_equal_test.cpp +++ b/test/detail_relaxed_equal_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" using namespace boost::histogram::detail; diff --git a/test/detail_replace_type_test.cpp b/test/detail_replace_type_test.cpp index 6318e11f..db1ab8ad 100644 --- a/test/detail_replace_type_test.cpp +++ b/test/detail_replace_type_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" using namespace boost::histogram::detail; diff --git a/test/detail_sub_array_and_span_test.cpp b/test/detail_static_vector_and_span_test.cpp similarity index 81% rename from test/detail_sub_array_and_span_test.cpp rename to test/detail_static_vector_and_span_test.cpp index 5dd1758b..fc3314fc 100644 --- a/test/detail_sub_array_and_span_test.cpp +++ b/test/detail_static_vector_and_span_test.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include @@ -12,7 +12,7 @@ namespace boost { namespace histogram { namespace detail { template -std::ostream& operator<<(std::ostream& os, const sub_array&) { +std::ostream& operator<<(std::ostream& os, const static_vector&) { return os; } std::ostream& operator<<(std::ostream& os, const reduce_command&) { return os; } @@ -26,20 +26,20 @@ int main() { using boost::span; { - sub_array a = {1, 2}; + static_vector a = {1, 2}; BOOST_TEST_EQ(a.size(), 2); BOOST_TEST_EQ(a.max_size(), 3); BOOST_TEST_EQ(a.at(0), 1); BOOST_TEST_EQ(a.at(1), 2); - sub_array b = {1, 2}; + static_vector b = {1, 2}; BOOST_TEST_EQ(a, b); - sub_array c = {1, 2, 3}; + static_vector c = {1, 2, 3}; BOOST_TEST_NE(a, c); - sub_array d = {2, 2}; + static_vector d = {2, 2}; BOOST_TEST_NE(a, d); auto sp = span(a); @@ -55,8 +55,8 @@ int main() { } { - sub_array a(2, 1); - sub_array b(1, 2); + static_vector a(2, 1); + static_vector b(1, 2); std::swap(a, b); BOOST_TEST_EQ(a.size(), 1); @@ -73,7 +73,7 @@ int main() { } { - const sub_array a(2); + const static_vector a(2); auto sp = span(a); BOOST_TEST_EQ(sp.size(), 2); } diff --git a/test/detail_tuple_slice_test.cpp b/test/detail_tuple_slice_test.cpp index 465e0f83..1ab3f59a 100644 --- a/test/detail_tuple_slice_test.cpp +++ b/test/detail_tuple_slice_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "std_ostream.hpp" +#include "ostream.hpp" using namespace boost::histogram::detail; diff --git a/test/histogram_collector_test.cpp b/test/histogram_collector_test.cpp new file mode 100644 index 00000000..c27b7ee7 --- /dev/null +++ b/test/histogram_collector_test.cpp @@ -0,0 +1,49 @@ +// Copyright 2024 Ruggero Turra, Hans Dembinski +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include "throw_exception.hpp" + +#include +#include + +using namespace boost::histogram; + +int main() { + using collector_t = accumulators::collector<>; + + auto h = make_histogram_with(dense_storage(), axis::integer<>(0, 5)); + + h(0, sample(1.1)); + h(0, sample(2.2)); + h(1, sample(10.10)); + + BOOST_TEST_EQ(h.at(0).count(), 2); + BOOST_TEST_EQ(h.at(1).count(), 1); + BOOST_TEST_EQ(h.at(2).count(), 0); + + BOOST_TEST_EQ(h.at(0), collector_t({1.1, 2.2})); + BOOST_TEST_EQ(h.at(1)[0], 10.10); + + std::vector x = {0, 1, 0, 2, 0}; + std::array data = {-1.1, -1.2, -1.3, -1.4, -1.5}; + h.fill(x, sample(data)); + + BOOST_TEST_EQ(h.at(0).count(), 5); + BOOST_TEST_EQ(h.at(1).count(), 2); + BOOST_TEST_EQ(h.at(2).count(), 1); + BOOST_TEST_NE(h.at(0), collector_t({1.1, 2.2})); + BOOST_TEST_EQ(h.at(0), collector_t({1.1, 2.2, -1.1, -1.3, -1.5})); + BOOST_TEST_EQ(h.at(1), collector_t({10.10, -1.2})); + BOOST_TEST_EQ(h.at(2), collector_t({-1.4})); + + return boost::report_errors(); +} diff --git a/test/histogram_growing_test.cpp b/test/histogram_growing_test.cpp index 87c21279..573b6d0d 100644 --- a/test/histogram_growing_test.cpp +++ b/test/histogram_growing_test.cpp @@ -13,7 +13,7 @@ #include #include #include "histogram.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/histogram_operators_test.cpp b/test/histogram_operators_test.cpp index 4fac0b0b..585260f2 100644 --- a/test/histogram_operators_test.cpp +++ b/test/histogram_operators_test.cpp @@ -13,11 +13,11 @@ #include #include #include -#include #include +#include #include "dummy_storage.hpp" #include "histogram.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index 484d2c4e..7a0006f8 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -25,7 +25,7 @@ #include "axis.hpp" #include "histogram.hpp" #include "is_close.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/odr_test.cpp b/test/odr_test.cpp index ae9a0dbd..c21432bf 100644 --- a/test/odr_test.cpp +++ b/test/odr_test.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/test/std_ostream.hpp b/test/ostream.hpp similarity index 54% rename from test/std_ostream.hpp rename to test/ostream.hpp index 8a181d98..d17ad61a 100644 --- a/test/std_ostream.hpp +++ b/test/ostream.hpp @@ -4,10 +4,13 @@ // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_HISTOGRAM_TEST_STD_OSTREAM_HPP -#define BOOST_HISTOGRAM_TEST_STD_OSTREAM_HPP +#ifndef BOOST_HISTOGRAM_TEST_OSTREAM_HPP +#define BOOST_HISTOGRAM_TEST_OSTREAM_HPP +#include +#include #include +#include #include #include #include @@ -23,7 +26,7 @@ ostream& operator<<(ostream& os, const vector& v) { } template -ostream& operator<<(ostream& os, const std::tuple& t) { +ostream& operator<<(ostream& os, const tuple& t) { os << "[ "; ::boost::mp11::tuple_for_each(t, [&os](const auto& x) { os << x << " "; }); os << "]"; @@ -31,10 +34,28 @@ ostream& operator<<(ostream& os, const std::tuple& t) { } template -ostream& operator<<(ostream& os, const std::pair& t) { +ostream& operator<<(ostream& os, const pair& t) { os << "[ " << t.first << " " << t.second << " ]"; return os; } + +template +ostream& operator<<(ostream& os, const array& v) { + os << "[ "; + for (const auto& x : v) os << x << " "; + os << "]"; + return os; +} } // namespace std +namespace boost { +template +std::ostream& operator<<(std::ostream& os, const span& v) { + os << "[ "; + for (const auto& x : v) os << x << " "; + os << "]"; + return os; +} +} // namespace boost + #endif diff --git a/test/tools_test.cpp b/test/tools_test.cpp index 005dbb3e..2df3729d 100644 --- a/test/tools_test.cpp +++ b/test/tools_test.cpp @@ -9,7 +9,7 @@ #include #include #include "allocator.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" using namespace boost::histogram; diff --git a/test/unlimited_storage_test.cpp b/test/unlimited_storage_test.cpp index 8e6c274c..560405d9 100644 --- a/test/unlimited_storage_test.cpp +++ b/test/unlimited_storage_test.cpp @@ -18,7 +18,7 @@ #include #include #include "allocator.hpp" -#include "std_ostream.hpp" +#include "ostream.hpp" #include "throw_exception.hpp" namespace boost { diff --git a/test/utility_clopper_pearson_interval_test.cpp b/test/utility_clopper_pearson_interval_test.cpp index e75e7fe2..b068e38b 100644 --- a/test/utility_clopper_pearson_interval_test.cpp +++ b/test/utility_clopper_pearson_interval_test.cpp @@ -18,46 +18,46 @@ template void test() { const T atol = 0.001; - clopper_pearson_interval iv(deviation{1}); + clopper_pearson_interval iv(deviation{1.f}); { - const auto x = iv(0, 1); - BOOST_TEST_IS_CLOSE(x.first, 0.0, atol); - BOOST_TEST_IS_CLOSE(x.second, 0.841, atol); + const auto x = iv(0.f, 1.f); + BOOST_TEST_IS_CLOSE(x.first, 0.f, atol); + BOOST_TEST_IS_CLOSE(x.second, 0.841f, atol); - fraction f(0, 1); + fraction f(0.f, 1.f); const auto y = iv(f); - BOOST_TEST_IS_CLOSE(y.first, 0.0, atol); - BOOST_TEST_IS_CLOSE(y.second, 0.841, atol); + BOOST_TEST_IS_CLOSE(y.first, 0.f, atol); + BOOST_TEST_IS_CLOSE(y.second, 0.841f, atol); } { - const auto x = iv(1, 0); - BOOST_TEST_IS_CLOSE(x.first, 0.158, atol); - BOOST_TEST_IS_CLOSE(x.second, 1.0, atol); + const auto x = iv(1.f, 0.f); + BOOST_TEST_IS_CLOSE(x.first, 0.158f, atol); + BOOST_TEST_IS_CLOSE(x.second, 1.f, atol); - fraction f(1, 0); + fraction f(1.f, 0.f); const auto y = iv(f); - BOOST_TEST_IS_CLOSE(y.first, 0.158, atol); - BOOST_TEST_IS_CLOSE(y.second, 1.0, atol); + BOOST_TEST_IS_CLOSE(y.first, 0.158f, atol); + BOOST_TEST_IS_CLOSE(y.second, 1.f, atol); } { - const auto x = iv(5, 5); - BOOST_TEST_IS_CLOSE(x.first, 0.304, atol); - BOOST_TEST_IS_CLOSE(x.second, 0.695, atol); + const auto x = iv(5.f, 5.f); + BOOST_TEST_IS_CLOSE(x.first, 0.304f, atol); + BOOST_TEST_IS_CLOSE(x.second, 0.695f, atol); } { - const auto x = iv(1, 9); - BOOST_TEST_IS_CLOSE(x.first, 0.017, atol); - BOOST_TEST_IS_CLOSE(x.second, 0.294, atol); + const auto x = iv(1.f, 9.f); + BOOST_TEST_IS_CLOSE(x.first, 0.017f, atol); + BOOST_TEST_IS_CLOSE(x.second, 0.294f, atol); } { - const auto x = iv(9, 1); - BOOST_TEST_IS_CLOSE(x.first, 0.705, atol); - BOOST_TEST_IS_CLOSE(x.second, 0.982, atol); + const auto x = iv(9.f, 1.f); + BOOST_TEST_IS_CLOSE(x.first, 0.705f, atol); + BOOST_TEST_IS_CLOSE(x.second, 0.982f, atol); } }