Skip to content

Commit 90867e2

Browse files
wisoHDembinski
andauthored
Add collector accumulator (#390)
Co-authored-by: Hans Dembinski <[email protected]>
1 parent cd3e111 commit 90867e2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+578
-86
lines changed

doc/changelog.qbk

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* Added new `accumulators::fraction` to compute fractions, their variance, and confidence intervals
2121
* 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
2222
* 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
23-
* Fixed internal `sub_array` and `span` in C++20
23+
* Fixed internal `static_vector` and `span` in C++20
2424

2525
[heading Boost 1.80]
2626

doc/concepts/Accumulator.qbk

+1
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,6 @@ An [*Accumulator] is a functor which consumes the argument to update some intern
9292
* [classref boost::histogram::accumulators::weighted_sum]
9393
* [classref boost::histogram::accumulators::mean]
9494
* [classref boost::histogram::accumulators::weighted_mean]
95+
* [classref boost::histogram::accumulators::collector]
9596

9697
[endsect]

doc/guide.qbk

+1
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ The library provides several accumulators:
449449
* [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.
450450
* [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
451451
`boost/histogram/utility`. Beware: one cannot pass `std::vector<bool>` 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.
452+
* [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).
452453

453454
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.
454455

include/boost/histogram/accumulators.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
[1]: histogram/reference.html#header.boost.histogram.accumulators.ostream_hpp
1818
*/
1919

20+
#include <boost/histogram/accumulators/collector.hpp>
2021
#include <boost/histogram/accumulators/count.hpp>
2122
#include <boost/histogram/accumulators/fraction.hpp>
2223
#include <boost/histogram/accumulators/mean.hpp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2024 Ruggero Turra, Hans Dembinski
2+
//
3+
// Distributed under the Boost Software License, version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_COLLECTOR_HPP
8+
#define BOOST_HISTOGRAM_ACCUMULATORS_COLLECTOR_HPP
9+
10+
#include <algorithm> // for std::equal
11+
#include <boost/core/nvp.hpp>
12+
#include <boost/histogram/detail/detect.hpp>
13+
#include <boost/histogram/fwd.hpp> // for collector<>
14+
#include <initializer_list>
15+
#include <type_traits>
16+
17+
namespace boost {
18+
namespace histogram {
19+
namespace accumulators {
20+
21+
/** Collects samples.
22+
23+
Input samples are stored in an internal container for later retrival, which stores the
24+
values consecutively in memory. The interface is designed to work with std::vector and
25+
other containers which implement the same API.
26+
27+
Warning: The memory of the accumulator is unbounded.
28+
*/
29+
template <class ContainerType>
30+
class collector {
31+
public:
32+
using container_type = ContainerType;
33+
using value_type = typename container_type::value_type;
34+
using allocator_type = typename container_type::allocator_type;
35+
using const_reference = typename container_type::const_reference;
36+
using iterator = typename container_type::iterator;
37+
using const_iterator = typename container_type::const_iterator;
38+
using size_type = typename container_type::size_type;
39+
using const_pointer = typename container_type::const_pointer;
40+
41+
// make template only match if forwarding args to container is valid
42+
template <typename... Args, class = decltype(container_type(std::declval<Args>()...))>
43+
explicit collector(Args&&... args) : container_(std::forward<Args>(args)...) {}
44+
45+
// make template only match if forwarding args to container is valid
46+
template <class T, typename... Args, class = decltype(container_type(std::initializer_list<T>(),std::declval<Args>()...))>
47+
explicit collector(std::initializer_list<T> list, Args&&... args)
48+
: container_(list, std::forward<Args>(args)...) {}
49+
50+
/// Append sample x.
51+
void operator()(const_reference x) { container_.push_back(x); }
52+
53+
/// Append samples from another collector.
54+
template <class C>
55+
collector& operator+=(const collector<C>& rhs) {
56+
container_.reserve(size() + rhs.size());
57+
container_.insert(end(), rhs.begin(), rhs.end());
58+
return *this;
59+
}
60+
61+
/// Return true if collections are equal.
62+
///
63+
/// Two collections are equal if they have the same number of elements
64+
/// which all compare equal.
65+
template <class Iterable, class = detail::is_iterable<Iterable>>
66+
bool operator==(const Iterable& rhs) const noexcept {
67+
return std::equal(begin(), end(), rhs.begin(), rhs.end());
68+
}
69+
70+
/// Return true if collections are not equal.
71+
template <class Iterable, class = detail::is_iterable<Iterable>>
72+
bool operator!=(const Iterable& rhs) const noexcept {
73+
return !operator==(rhs);
74+
}
75+
76+
/// Return number of samples.
77+
size_type size() const noexcept { return container_.size(); }
78+
79+
/// Return number of samples (alias for size()).
80+
size_type count() const noexcept { return container_.size(); }
81+
82+
/// Return readonly iterator to start of collection.
83+
const const_iterator begin() const noexcept { return container_.begin(); }
84+
85+
/// Return readonly iterator to end of collection.
86+
const const_iterator end() const noexcept { return container_.end(); }
87+
88+
/// Return const reference to value at index.
89+
const_reference operator[](size_type idx) const noexcept { return container_[idx]; }
90+
91+
/// Return pointer to internal memory.
92+
const_pointer data() const noexcept { return container_.data(); }
93+
94+
allocator_type get_allocator() const { return container_.get_allocator(); }
95+
96+
template <class Archive>
97+
void serialize(Archive& ar, unsigned version) {
98+
(void)version;
99+
ar& make_nvp("container", container_);
100+
}
101+
102+
private:
103+
container_type container_;
104+
};
105+
106+
} // namespace accumulators
107+
} // namespace histogram
108+
} // namespace boost
109+
110+
#endif

include/boost/histogram/accumulators/ostream.hpp

+14
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
101101
return detail::handle_nonzero_width(os, x);
102102
}
103103

104+
template <class CharT, class Traits, class U>
105+
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
106+
const collector<U>& x) {
107+
if (os.width() == 0) {
108+
os << "collector{";
109+
auto iter = x.begin();
110+
if (iter != x.end()) os << *iter++;
111+
for (; iter != x.end(); ++iter) os << ", " << *iter;
112+
os << "}";
113+
return os;
114+
}
115+
return detail::handle_nonzero_width(os, x);
116+
}
117+
104118
} // namespace accumulators
105119
} // namespace histogram
106120
} // namespace boost

include/boost/histogram/detail/axes.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include <boost/histogram/detail/priority.hpp>
1818
#include <boost/histogram/detail/relaxed_tuple_size.hpp>
1919
#include <boost/histogram/detail/static_if.hpp>
20-
#include <boost/histogram/detail/sub_array.hpp>
20+
#include <boost/histogram/detail/static_vector.hpp>
2121
#include <boost/histogram/detail/try_cast.hpp>
2222
#include <boost/histogram/fwd.hpp>
2323
#include <boost/mp11/algorithm.hpp>
@@ -381,13 +381,13 @@ std::size_t offset(const T& axes) {
381381
// make default-constructed buffer (no initialization for POD types)
382382
template <class T, class A>
383383
auto make_stack_buffer(const A& a) {
384-
return sub_array<T, buffer_size<A>::value>(axes_rank(a));
384+
return static_vector<T, buffer_size<A>::value>(axes_rank(a));
385385
}
386386

387387
// make buffer with elements initialized to v
388388
template <class T, class A>
389389
auto make_stack_buffer(const A& a, const T& t) {
390-
return sub_array<T, buffer_size<A>::value>(axes_rank(a), t);
390+
return static_vector<T, buffer_size<A>::value>(axes_rank(a), t);
391391
}
392392

393393
template <class T>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2019 Hans Dembinski
2+
//
3+
// Distributed under the Boost Software License, version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#ifndef BOOST_HISTOGRAM_DETAIL_CHUNK_VECTOR_HPP
8+
#define BOOST_HISTOGRAM_DETAIL_CHUNK_VECTOR_HPP
9+
10+
#include <boost/core/span.hpp>
11+
#include <boost/throw_exception.hpp>
12+
#include <stdexcept>
13+
#include <vector>
14+
15+
namespace boost {
16+
namespace histogram {
17+
namespace detail {
18+
19+
// Warning: this is not a proper container and is only used to
20+
// test the feasibility of using accumulators::collector with a
21+
// custom container type. If time permits, this will be expanded
22+
// into a proper container type.
23+
template <class ValueType>
24+
class chunk_vector {
25+
public:
26+
using base = std::vector<ValueType>;
27+
using allocator_type = typename base::allocator_type;
28+
using pointer = typename base::pointer;
29+
using const_pointer = typename base::const_pointer;
30+
using size_type = typename base::size_type;
31+
using const_reference = boost::span<const ValueType>;
32+
using reference = boost::span<ValueType>;
33+
// this is wrong and should make a copy; it is not a problem for
34+
// the current use-case, but a general purpose implementation cannot
35+
// violate concepts like this
36+
using value_type = const_reference;
37+
38+
template <class Pointer>
39+
struct iterator_t {
40+
iterator_t& operator++() {
41+
ptr_ += chunk_;
42+
return *this;
43+
}
44+
45+
iterator_t operator++(int) {
46+
iterator_t copy(*this);
47+
ptr_ += chunk_;
48+
return copy;
49+
}
50+
51+
value_type operator*() const { return value_type(ptr_, ptr_ + chunk_); }
52+
53+
Pointer ptr_;
54+
size_type chunk_;
55+
};
56+
57+
using iterator = iterator_t<pointer>;
58+
using const_iterator = iterator_t<const_pointer>;
59+
60+
// this creates an empty chunk_vector
61+
explicit chunk_vector(size_type chunk, const allocator_type& alloc = {})
62+
: chunk_(chunk), vec_(alloc) {}
63+
64+
chunk_vector(std::initializer_list<value_type> list, size_type chunk,
65+
const allocator_type& alloc = {})
66+
: chunk_(chunk), vec_(list, alloc) {}
67+
68+
allocator_type get_allocator() noexcept(noexcept(allocator_type())) {
69+
return vec_.get_allocator();
70+
}
71+
72+
void push_back(const_reference x) {
73+
if (x.size() != chunk_)
74+
BOOST_THROW_EXCEPTION(std::runtime_error("argument has wrong size"));
75+
// we don't use std::vector::insert here to have amortized constant complexity
76+
for (auto&& elem : x) vec_.push_back(elem);
77+
}
78+
79+
auto insert(const_iterator pos, const_iterator o_begin, const_iterator o_end) {
80+
if (std::distance(o_begin, o_end) % chunk_ == 0)
81+
BOOST_THROW_EXCEPTION(std::runtime_error("argument has wrong size"));
82+
return vec_.insert(pos, o_begin, o_end);
83+
}
84+
85+
const_iterator begin() const noexcept { return {vec_.data(), chunk_}; }
86+
const_iterator end() const noexcept { return {vec_.data() + vec_.size(), chunk_}; }
87+
88+
value_type operator[](size_type idx) const noexcept {
89+
return {vec_.data() + idx * chunk_, vec_.data() + (idx + 1) * chunk_};
90+
}
91+
92+
size_type size() const noexcept { return vec_.size() / chunk_; }
93+
94+
private:
95+
size_type chunk_;
96+
base vec_;
97+
};
98+
99+
} // namespace detail
100+
} // namespace histogram
101+
} // namespace boost
102+
103+
#endif

include/boost/histogram/detail/detect.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ struct detect_base {
6666
// reset has overloads, trying to get pmf in this case always fails
6767
BOOST_HISTOGRAM_DETAIL_DETECT(has_method_reset, t.reset(0));
6868

69+
BOOST_HISTOGRAM_DETAIL_DETECT(has_method_push_back, (&T::push_back));
70+
6971
BOOST_HISTOGRAM_DETAIL_DETECT(is_indexable, t[0]);
7072

7173
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(is_transform, (t.inverse(t.forward(u))));

include/boost/histogram/detail/sub_array.hpp renamed to include/boost/histogram/detail/static_vector.hpp

+18-16
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
// (See accompanying file LICENSE_1_0.txt
55
// or copy at http://www.boost.org/LICENSE_1_0.txt)
66

7-
#ifndef BOOST_HISTOGRAM_DETAIL_SUB_ARRAY_HPP
8-
#define BOOST_HISTOGRAM_DETAIL_SUB_ARRAY_HPP
7+
#ifndef BOOST_HISTOGRAM_DETAIL_STATIC_VECTOR_HPP
8+
#define BOOST_HISTOGRAM_DETAIL_STATIC_VECTOR_HPP
99

1010
#include <algorithm>
1111
#include <boost/throw_exception.hpp>
@@ -15,10 +15,11 @@ namespace boost {
1515
namespace histogram {
1616
namespace detail {
1717

18-
// Like std::array, but allows to use less than maximum capacity.
19-
// Cannot inherit from std::array, since this confuses span.
18+
// A crude implementation of boost::container::static_vector.
19+
// Like std::vector, but with static allocation up to a maximum capacity.
2020
template <class T, std::size_t N>
21-
class sub_array {
21+
class static_vector {
22+
// Cannot inherit from std::array, since this confuses span.
2223
static constexpr bool swap_element_is_noexcept() noexcept {
2324
using std::swap;
2425
return noexcept(swap(std::declval<T&>(), std::declval<T&>()));
@@ -34,19 +35,19 @@ class sub_array {
3435
using iterator = pointer;
3536
using const_iterator = const_pointer;
3637

37-
sub_array() = default;
38+
static_vector() = default;
3839

39-
explicit sub_array(std::size_t s) noexcept : size_(s) { assert(size_ <= N); }
40+
explicit static_vector(std::size_t s) noexcept : size_(s) { assert(size_ <= N); }
4041

41-
sub_array(std::size_t s, const T& value) noexcept(
42+
static_vector(std::size_t s, const T& value) noexcept(
4243
std::is_nothrow_assignable<T, const_reference>::value)
43-
: sub_array(s) {
44+
: static_vector(s) {
4445
fill(value);
4546
}
4647

47-
sub_array(std::initializer_list<T> il) noexcept(
48+
static_vector(std::initializer_list<T> il) noexcept(
4849
std::is_nothrow_assignable<T, const_reference>::value)
49-
: sub_array(il.size()) {
50+
: static_vector(il.size()) {
5051
std::copy(il.begin(), il.end(), data_);
5152
}
5253

@@ -90,7 +91,7 @@ class sub_array {
9091
std::fill(begin(), end(), value);
9192
}
9293

93-
void swap(sub_array& other) noexcept(swap_element_is_noexcept()) {
94+
void swap(static_vector& other) noexcept(swap_element_is_noexcept()) {
9495
using std::swap;
9596
const size_type s = (std::max)(size(), other.size());
9697
for (auto i = begin(), j = other.begin(), end = begin() + s; i != end; ++i, ++j)
@@ -104,12 +105,12 @@ class sub_array {
104105
};
105106

106107
template <class T, std::size_t N>
107-
bool operator==(const sub_array<T, N>& a, const sub_array<T, N>& b) noexcept {
108+
bool operator==(const static_vector<T, N>& a, const static_vector<T, N>& b) noexcept {
108109
return std::equal(a.begin(), a.end(), b.begin(), b.end());
109110
}
110111

111112
template <class T, std::size_t N>
112-
bool operator!=(const sub_array<T, N>& a, const sub_array<T, N>& b) noexcept {
113+
bool operator!=(const static_vector<T, N>& a, const static_vector<T, N>& b) noexcept {
113114
return !(a == b);
114115
}
115116

@@ -119,8 +120,9 @@ bool operator!=(const sub_array<T, N>& a, const sub_array<T, N>& b) noexcept {
119120

120121
namespace std {
121122
template <class T, std::size_t N>
122-
void swap(::boost::histogram::detail::sub_array<T, N>& a,
123-
::boost::histogram::detail::sub_array<T, N>& b) noexcept(noexcept(a.swap(b))) {
123+
void swap(
124+
::boost::histogram::detail::static_vector<T, N>& a,
125+
::boost::histogram::detail::static_vector<T, N>& b) noexcept(noexcept(a.swap(b))) {
124126
a.swap(b);
125127
}
126128
} // namespace std

0 commit comments

Comments
 (0)