Skip to content

Commit bf7712f

Browse files
committed
regular and variable axis are closed intervals if overflow bin is absent (#344)
1 parent b482598 commit bf7712f

File tree

7 files changed

+235
-112
lines changed

7 files changed

+235
-112
lines changed

benchmark/histogram_filling.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,33 @@ using DStore = boost::histogram::adaptive_storage<>;
3131
#endif
3232

3333
using namespace boost::histogram;
34+
namespace op = boost::histogram::axis::option;
3435
using reg = axis::regular<>;
36+
using reg_closed =
37+
axis::regular<double, boost::use_default, boost::use_default, op::none_t>;
38+
39+
class reg_closed_unsafe {
40+
public:
41+
reg_closed_unsafe(axis::index_type n, double start, double stop)
42+
: min_{start}, delta_{stop - start}, size_{n} {}
43+
44+
axis::index_type index(double x) const noexcept {
45+
// Runs in hot loop, please measure impact of changes
46+
auto z = (x - min_) / delta_;
47+
// assume that z < 0 and z > 1 never happens, promised by inclusive()
48+
if (z == 1) return size() - 1;
49+
return static_cast<axis::index_type>(z * size());
50+
}
51+
52+
axis::index_type size() const noexcept { return size_; }
53+
54+
static constexpr bool inclusive() { return true; }
55+
56+
private:
57+
double min_;
58+
double delta_;
59+
axis::index_type size_;
60+
};
3561

3662
template <class Distribution, class Tag, class Storage = SStore>
3763
static void fill_1d(benchmark::State& state) {
@@ -41,6 +67,22 @@ static void fill_1d(benchmark::State& state) {
4167
state.SetItemsProcessed(state.iterations());
4268
}
4369

70+
template <class Distribution, class Tag, class Storage = SStore>
71+
static void fill_1d_closed(benchmark::State& state) {
72+
auto h = make_s(Tag(), Storage(), reg_closed(100, 0, 1));
73+
auto gen = generator<Distribution>();
74+
for (auto _ : state) benchmark::DoNotOptimize(h(gen()));
75+
state.SetItemsProcessed(state.iterations());
76+
}
77+
78+
template <class Distribution, class Tag, class Storage = SStore>
79+
static void fill_1d_closed_unsafe(benchmark::State& state) {
80+
auto h = make_s(Tag(), Storage(), reg_closed_unsafe(100, 0, 1));
81+
auto gen = generator<Distribution>();
82+
for (auto _ : state) benchmark::DoNotOptimize(h(gen()));
83+
state.SetItemsProcessed(state.iterations());
84+
}
85+
4486
template <class Distribution, class Tag, class Storage = SStore>
4587
static void fill_n_1d(benchmark::State& state) {
4688
auto h = make_s(Tag(), Storage(), reg(100, 0, 1));
@@ -49,6 +91,22 @@ static void fill_n_1d(benchmark::State& state) {
4991
state.SetItemsProcessed(state.iterations() * gen.size());
5092
}
5193

94+
template <class Distribution, class Tag, class Storage = SStore>
95+
static void fill_n_1d_closed(benchmark::State& state) {
96+
auto h = make_s(Tag(), Storage(), reg_closed(100, 0, 1));
97+
auto gen = generator<Distribution>();
98+
for (auto _ : state) h.fill(gen);
99+
state.SetItemsProcessed(state.iterations() * gen.size());
100+
}
101+
102+
template <class Distribution, class Tag, class Storage = SStore>
103+
static void fill_n_1d_closed_unsafe(benchmark::State& state) {
104+
auto h = make_s(Tag(), Storage(), reg_closed_unsafe(100, 0, 1));
105+
auto gen = generator<Distribution>();
106+
for (auto _ : state) h.fill(gen);
107+
state.SetItemsProcessed(state.iterations() * gen.size());
108+
}
109+
52110
template <class Distribution, class Tag, class Storage = SStore>
53111
static void fill_2d(benchmark::State& state) {
54112
auto h = make_s(Tag(), Storage(), reg(100, 0, 1), reg(100, 0, 1));
@@ -111,6 +169,8 @@ BENCHMARK_TEMPLATE(fill_1d, uniform, dynamic_tag);
111169
// BENCHMARK_TEMPLATE(fill_1d, uniform, dynamic_tag, DStore);
112170
BENCHMARK_TEMPLATE(fill_1d, normal, dynamic_tag);
113171
// BENCHMARK_TEMPLATE(fill_1d, normal, dynamic_tag, DStore);
172+
BENCHMARK_TEMPLATE(fill_1d_closed, uniform, static_tag);
173+
BENCHMARK_TEMPLATE(fill_1d_closed_unsafe, uniform, static_tag);
114174

115175
BENCHMARK_TEMPLATE(fill_n_1d, uniform, static_tag);
116176
// BENCHMARK_TEMPLATE(fill_n_1d, uniform, static_tag, DStore);
@@ -120,6 +180,8 @@ BENCHMARK_TEMPLATE(fill_n_1d, uniform, dynamic_tag);
120180
// BENCHMARK_TEMPLATE(fill_n_1d, uniform, dynamic_tag, DStore);
121181
BENCHMARK_TEMPLATE(fill_n_1d, normal, dynamic_tag);
122182
// BENCHMARK_TEMPLATE(fill_n_1d, normal, dynamic_tag, DStore);
183+
BENCHMARK_TEMPLATE(fill_n_1d_closed, uniform, static_tag);
184+
BENCHMARK_TEMPLATE(fill_n_1d_closed_unsafe, uniform, static_tag);
123185

124186
BENCHMARK_TEMPLATE(fill_2d, uniform, static_tag);
125187
// BENCHMARK_TEMPLATE(fill_2d, uniform, static_tag, DStore);

include/boost/histogram/axis/category.hpp

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,21 @@ namespace boost {
2626
namespace histogram {
2727
namespace axis {
2828

29-
/**
30-
Maps at a set of unique values to bin indices.
31-
32-
The axis maps a set of values to bins, following the order of arguments in the
33-
constructor. The optional overflow bin for this axis counts input values that
34-
are not part of the set. Binning has O(N) complexity, but with a very small
35-
factor. For small N (the typical use case) it beats other kinds of lookup.
36-
37-
@tparam Value input value type, must be equal-comparable.
38-
@tparam MetaData type to store meta data.
39-
@tparam Options see boost::histogram::axis::option.
40-
@tparam Allocator allocator to use for dynamic memory management.
41-
42-
The options `underflow` and `circular` are not allowed. The options `growth`
43-
and `overflow` are mutually exclusive.
44-
*/
29+
/** Maps at a set of unique values to bin indices.
30+
31+
The axis maps a set of values to bins, following the order of arguments in the
32+
constructor. The optional overflow bin for this axis counts input values that
33+
are not part of the set. Binning has O(N) complexity, but with a very small
34+
factor. For small N (the typical use case) it beats other kinds of lookup.
35+
36+
@tparam Value input value type, must be equal-comparable.
37+
@tparam MetaData type to store meta data.
38+
@tparam Options see boost::histogram::axis::option.
39+
@tparam Allocator allocator to use for dynamic memory management.
40+
41+
The options `underflow` and `circular` are not allowed. The options `growth`
42+
and `overflow` are mutually exclusive.
43+
*/
4544
template <class Value, class MetaData, class Options, class Allocator>
4645
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
4746
public metadata_base_t<MetaData> {
@@ -66,12 +65,12 @@ class category : public iterator_mixin<category<Value, MetaData, Options, Alloca
6665
explicit category(allocator_type alloc) : vec_(alloc) {}
6766

6867
/** Construct from iterator range of unique values.
69-
*
70-
* @param begin begin of category range of unique values.
71-
* @param end end of category range of unique values.
72-
* @param meta description of the axis (optional).
73-
* @param options see boost::histogram::axis::option (optional).
74-
* @param alloc allocator instance to use (optional).
68+
69+
@param begin begin of category range of unique values.
70+
@param end end of category range of unique values.
71+
@param meta description of the axis (optional).
72+
@param options see boost::histogram::axis::option (optional).
73+
@param alloc allocator instance to use (optional).
7574
*/
7675
template <class It, class = detail::requires_iterator<It>>
7776
category(It begin, It end, metadata_type meta = {}, options_type options = {},
@@ -91,11 +90,11 @@ class category : public iterator_mixin<category<Value, MetaData, Options, Alloca
9190
: category(begin, end, std::move(meta), {}, std::move(alloc)) {}
9291

9392
/** Construct axis from iterable sequence of unique values.
94-
*
95-
* @param iterable sequence of unique values.
96-
* @param meta description of the axis.
97-
* @param options see boost::histogram::axis::option (optional).
98-
* @param alloc allocator instance to use.
93+
94+
@param iterable sequence of unique values.
95+
@param meta description of the axis.
96+
@param options see boost::histogram::axis::option (optional).
97+
@param alloc allocator instance to use.
9998
*/
10099
template <class C, class = detail::requires_iterable<C>>
101100
category(const C& iterable, metadata_type meta = {}, options_type options = {},
@@ -110,11 +109,11 @@ class category : public iterator_mixin<category<Value, MetaData, Options, Alloca
110109
std::move(alloc)) {}
111110

112111
/** Construct axis from an initializer list of unique values.
113-
*
114-
* @param list `std::initializer_list` of unique values.
115-
* @param meta description of the axis.
116-
* @param options see boost::histogram::axis::option (optional).
117-
* @param alloc allocator instance to use.
112+
113+
@param list `std::initializer_list` of unique values.
114+
@param meta description of the axis.
115+
@param options see boost::histogram::axis::option (optional).
116+
@param alloc allocator instance to use.
118117
*/
119118
template <class U>
120119
category(std::initializer_list<U> list, metadata_type meta = {},

include/boost/histogram/axis/integer.hpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@ namespace boost {
2929
namespace histogram {
3030
namespace axis {
3131

32-
/**
33-
Axis for an interval of integer values with unit steps.
32+
/** Axis for an interval of integer values with unit steps.
3433
35-
Binning is a O(1) operation. This axis bins faster than a regular axis.
34+
Binning is a O(1) operation. This axis bins faster than a regular axis.
3635
37-
@tparam Value input value type. Must be integer or floating point.
38-
@tparam MetaData type to store meta data.
39-
@tparam Options see boost::histogram::axis::option.
36+
@tparam Value input value type. Must be integer or floating point.
37+
@tparam MetaData type to store meta data.
38+
@tparam Options see boost::histogram::axis::option.
4039
*/
4140
template <class Value, class MetaData, class Options>
4241
class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
@@ -72,11 +71,11 @@ class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
7271
constexpr integer() = default;
7372

7473
/** Construct over semi-open integer interval [start, stop).
75-
*
76-
* @param start first integer of covered range.
77-
* @param stop one past last integer of covered range.
78-
* @param meta description of the axis (optional).
79-
* @param options see boost::histogram::axis::option (optional).
74+
75+
@param start first integer of covered range.
76+
@param stop one past last integer of covered range.
77+
@param meta description of the axis (optional).
78+
@param options see boost::histogram::axis::option (optional).
8079
*/
8180
integer(value_type start, value_type stop, metadata_type meta = {},
8281
options_type options = {})

include/boost/histogram/axis/regular.hpp

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,22 @@ step_type<T> step(T t) {
165165
return step_type<T>{t};
166166
}
167167

168-
/**
169-
Axis for equidistant intervals on the real line.
168+
/** Axis for equidistant intervals on the real line.
169+
170+
The most common binning strategy. Very fast. Binning is a O(1) operation.
170171
171-
The most common binning strategy. Very fast. Binning is a O(1) operation.
172+
If the axis has an overflow bin (the default), a value on the upper edge of the last
173+
bin is put in the overflow bin. The axis range represents a semi-open interval.
172174
173-
@tparam Value input value type, must be floating point.
174-
@tparam Transform builtin or user-defined transform type.
175-
@tparam MetaData type to store meta data.
176-
@tparam Options see boost::histogram::axis::option.
175+
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
176+
still counted towards the last bin. The axis range represents a closed interval. This
177+
is the desired behavior for random numbers drawn from a bounded interval, which is
178+
usually closed.
179+
180+
@tparam Value input value type, must be floating point.
181+
@tparam Transform builtin or user-defined transform type.
182+
@tparam MetaData type to store meta data.
183+
@tparam Options see boost::histogram::axis::option.
177184
*/
178185
template <class Value, class Transform, class MetaData, class Options>
179186
class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
@@ -207,13 +214,13 @@ class regular : public iterator_mixin<regular<Value, Transform, MetaData, Option
207214
constexpr regular() = default;
208215

209216
/** Construct n bins over real transformed range [start, stop).
210-
*
211-
* @param trans transform instance to use.
212-
* @param n number of bins.
213-
* @param start low edge of first bin.
214-
* @param stop high edge of last bin.
215-
* @param meta description of the axis (optional).
216-
* @param options see boost::histogram::axis::option (optional).
217+
218+
@param trans transform instance to use.
219+
@param n number of bins.
220+
@param start low edge of first bin.
221+
@param stop high edge of last bin.
222+
@param meta description of the axis (optional).
223+
@param options see boost::histogram::axis::option (optional).
217224
*/
218225
regular(transform_type trans, unsigned n, value_type start, value_type stop,
219226
metadata_type meta = {}, options_type options = {})
@@ -232,30 +239,30 @@ class regular : public iterator_mixin<regular<Value, Transform, MetaData, Option
232239
}
233240

234241
/** Construct n bins over real range [start, stop).
235-
*
236-
* @param n number of bins.
237-
* @param start low edge of first bin.
238-
* @param stop high edge of last bin.
239-
* @param meta description of the axis (optional).
240-
* @param options see boost::histogram::axis::option (optional).
242+
243+
@param n number of bins.
244+
@param start low edge of first bin.
245+
@param stop high edge of last bin.
246+
@param meta description of the axis (optional).
247+
@param options see boost::histogram::axis::option (optional).
241248
*/
242249
regular(unsigned n, value_type start, value_type stop, metadata_type meta = {},
243250
options_type options = {})
244251
: regular({}, n, start, stop, std::move(meta), options) {}
245252

246253
/** Construct bins with the given step size over real transformed range
247-
* [start, stop).
248-
*
249-
* @param trans transform instance to use.
250-
* @param step width of a single bin.
251-
* @param start low edge of first bin.
252-
* @param stop upper limit of high edge of last bin (see below).
253-
* @param meta description of the axis (optional).
254-
* @param options see boost::histogram::axis::option (optional).
255-
*
256-
* The axis computes the number of bins as n = abs(stop - start) / step,
257-
* rounded down. This means that stop is an upper limit to the actual value
258-
* (start + n * step).
254+
[start, stop).
255+
256+
@param trans transform instance to use.
257+
@param step width of a single bin.
258+
@param start low edge of first bin.
259+
@param stop upper limit of high edge of last bin (see below).
260+
@param meta description of the axis (optional).
261+
@param options see boost::histogram::axis::option (optional).
262+
263+
The axis computes the number of bins as n = abs(stop - start) / step,
264+
rounded down. This means that stop is an upper limit to the actual value
265+
(start + n * step).
259266
*/
260267
template <class T>
261268
regular(transform_type trans, step_type<T> step, value_type start, value_type stop,
@@ -267,16 +274,16 @@ class regular : public iterator_mixin<regular<Value, Transform, MetaData, Option
267274
std::move(meta), options) {}
268275

269276
/** Construct bins with the given step size over real range [start, stop).
270-
*
271-
* @param step width of a single bin.
272-
* @param start low edge of first bin.
273-
* @param stop upper limit of high edge of last bin (see below).
274-
* @param meta description of the axis (optional).
275-
* @param options see boost::histogram::axis::option (optional).
276-
*
277-
* The axis computes the number of bins as n = abs(stop - start) / step,
278-
* rounded down. This means that stop is an upper limit to the actual value
279-
* (start + n * step).
277+
278+
@param step width of a single bin.
279+
@param start low edge of first bin.
280+
@param stop upper limit of high edge of last bin (see below).
281+
@param meta description of the axis (optional).
282+
@param options see boost::histogram::axis::option (optional).
283+
284+
The axis computes the number of bins as n = abs(stop - start) / step,
285+
rounded down. This means that stop is an upper limit to the actual value
286+
(start + n * step).
280287
*/
281288
template <class T>
282289
regular(step_type<T> step, value_type start, value_type stop, metadata_type meta = {},
@@ -311,6 +318,8 @@ class regular : public iterator_mixin<regular<Value, Transform, MetaData, Option
311318
else
312319
return -1;
313320
}
321+
// upper edge of last bin is inclusive if overflow bin is not present
322+
if (!options_type::test(option::overflow) && z == 1) return size() - 1;
314323
}
315324
return size(); // also returned if x is NaN
316325
}

0 commit comments

Comments
 (0)