1 // Copyright 2018-2019 Hans Dembinski
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)
7 #ifndef BOOST_HISTOGRAM_ALGORITHM_REDUCE_HPP
8 #define BOOST_HISTOGRAM_ALGORITHM_REDUCE_HPP
10 #include <boost/histogram/axis/traits.hpp>
11 #include <boost/histogram/detail/axes.hpp>
12 #include <boost/histogram/detail/make_default.hpp>
13 #include <boost/histogram/detail/reduce_command.hpp>
14 #include <boost/histogram/detail/static_if.hpp>
15 #include <boost/histogram/fwd.hpp>
16 #include <boost/histogram/indexed.hpp>
17 #include <boost/histogram/unsafe_access.hpp>
18 #include <boost/throw_exception.hpp>
21 #include <initializer_list>
29 /** Holder for a reduce command.
31 Use this type to store reduce commands in a container. The internals of this type are an
32 implementation detail.
34 using reduce_command = detail::reduce_command;
36 using reduce_option [[deprecated("use reduce_command instead")]] =
37 reduce_command; ///< deprecated
39 /** Shrink command to be used in `reduce`.
41 Command is applied to axis with given index.
43 Shrinking is based on an inclusive value interval. The bin which contains the first
44 value starts the range of bins to keep. The bin which contains the second value is the
45 last included in that range. When the second value is exactly equal to a lower bin edge,
46 then the previous bin is the last in the range.
48 The counts in removed bins are added to the corresponding underflow and overflow bins,
49 if they are present. If they are not present, the counts are discarded. Also see
50 `crop`, which always discards the counts.
52 @param iaxis which axis to operate on.
53 @param lower bin which contains lower is first to be kept.
54 @param upper bin which contains upper is last to be kept, except if upper is equal to
57 inline reduce_command shrink(unsigned iaxis, double lower, double upper) {
59 BOOST_THROW_EXCEPTION(std::invalid_argument("lower != upper required"));
62 r.range = reduce_command::range_t::values;
63 r.begin.value = lower;
70 /** Shrink command to be used in `reduce`.
72 Command is applied to corresponding axis in order of reduce arguments.
74 Shrinking is based on an inclusive value interval. The bin which contains the first
75 value starts the range of bins to keep. The bin which contains the second value is the
76 last included in that range. When the second value is exactly equal to a lower bin edge,
77 then the previous bin is the last in the range.
79 The counts in removed bins are added to the corresponding underflow and overflow bins,
80 if they are present. If they are not present, the counts are discarded. Also see
81 `crop`, which always discards the counts.
83 @param lower bin which contains lower is first to be kept.
84 @param upper bin which contains upper is last to be kept, except if upper is equal to
87 inline reduce_command shrink(double lower, double upper) {
88 return shrink(reduce_command::unset, lower, upper);
91 /** Crop command to be used in `reduce`.
93 Command is applied to axis with given index.
95 Works like `shrink` (see shrink documentation for details), but counts in removed
96 bins are always discarded, whether underflow and overflow bins are present or not.
98 @param iaxis which axis to operate on.
99 @param lower bin which contains lower is first to be kept.
100 @param upper bin which contains upper is last to be kept, except if upper is equal to
103 inline reduce_command crop(unsigned iaxis, double lower, double upper) {
104 reduce_command r = shrink(iaxis, lower, upper);
109 /** Crop command to be used in `reduce`.
111 Command is applied to corresponding axis in order of reduce arguments.
113 Works like `shrink` (see shrink documentation for details), but counts in removed bins
114 are discarded, whether underflow and overflow bins are present or not.
116 @param lower bin which contains lower is first to be kept.
117 @param upper bin which contains upper is last to be kept, except if upper is equal to
120 inline reduce_command crop(double lower, double upper) {
121 return crop(reduce_command::unset, lower, upper);
124 /// Whether to behave like `shrink` or `crop` regarding removed bins.
125 enum class slice_mode { shrink, crop };
127 /** Slice command to be used in `reduce`.
129 Command is applied to axis with given index.
131 Slicing works like `shrink` or `crop`, but uses bin indices instead of values.
133 @param iaxis which axis to operate on.
134 @param begin first index that should be kept.
135 @param end one past the last index that should be kept.
136 @param mode whether to behave like `shrink` or `crop` regarding removed bins.
138 inline reduce_command slice(unsigned iaxis, axis::index_type begin, axis::index_type end,
139 slice_mode mode = slice_mode::shrink) {
141 BOOST_THROW_EXCEPTION(std::invalid_argument("begin < end required"));
145 r.range = reduce_command::range_t::indices;
146 r.begin.index = begin;
149 r.crop = mode == slice_mode::crop;
153 /** Slice command to be used in `reduce`.
155 Command is applied to corresponding axis in order of reduce arguments.
157 Slicing works like `shrink` or `crop`, but uses bin indices instead of values.
159 @param begin first index that should be kept.
160 @param end one past the last index that should be kept.
161 @param mode whether to behave like `shrink` or `crop` regarding removed bins.
163 inline reduce_command slice(axis::index_type begin, axis::index_type end,
164 slice_mode mode = slice_mode::shrink) {
165 return slice(reduce_command::unset, begin, end, mode);
168 /** Rebin command to be used in `reduce`.
170 Command is applied to axis with given index.
172 The command merges N adjacent bins into one. This makes the axis coarser and the bins
173 wider. The original number of bins is divided by N. If there is a rest to this devision,
174 the axis is implicitly shrunk at the upper end by that rest.
176 @param iaxis which axis to operate on.
177 @param merge how many adjacent bins to merge into one.
179 inline reduce_command rebin(unsigned iaxis, unsigned merge) {
180 if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
184 r.range = reduce_command::range_t::none;
189 /** Rebin command to be used in `reduce`.
191 Command is applied to corresponding axis in order of reduce arguments.
193 The command merges N adjacent bins into one. This makes the axis coarser and the bins
194 wider. The original number of bins is divided by N. If there is a rest to this devision,
195 the axis is implicitly shrunk at the upper end by that rest.
197 @param merge how many adjacent bins to merge into one.
199 inline reduce_command rebin(unsigned merge) {
200 return rebin(reduce_command::unset, merge);
203 /** Shrink and rebin command to be used in `reduce`.
205 Command is applied to corresponding axis in order of reduce arguments.
207 To shrink(unsigned, double, double) and rebin(unsigned, unsigned) in one command (see
208 the respective commands for more details). Equivalent to passing both commands for the
209 same axis to `reduce`.
211 @param iaxis which axis to operate on.
212 @param lower lowest bound that should be kept.
213 @param upper highest bound that should be kept. If upper is inside bin interval, the
214 whole interval is removed.
215 @param merge how many adjacent bins to merge into one.
217 inline reduce_command shrink_and_rebin(unsigned iaxis, double lower, double upper,
219 reduce_command r = shrink(iaxis, lower, upper);
220 r.merge = rebin(merge).merge;
224 /** Shrink and rebin command to be used in `reduce`.
226 Command is applied to corresponding axis in order of reduce arguments.
228 To `shrink` and `rebin` in one command (see the respective commands for more
229 details). Equivalent to passing both commands for the same axis to `reduce`.
231 @param lower lowest bound that should be kept.
232 @param upper highest bound that should be kept. If upper is inside bin interval, the
233 whole interval is removed.
234 @param merge how many adjacent bins to merge into one.
236 inline reduce_command shrink_and_rebin(double lower, double upper, unsigned merge) {
237 return shrink_and_rebin(reduce_command::unset, lower, upper, merge);
240 /** Crop and rebin command to be used in `reduce`.
242 Command is applied to axis with given index.
244 To `crop` and `rebin` in one command (see the respective commands for more
245 details). Equivalent to passing both commands for the same axis to `reduce`.
247 @param iaxis which axis to operate on.
248 @param lower lowest bound that should be kept.
249 @param upper highest bound that should be kept. If upper is inside bin interval,
250 the whole interval is removed.
251 @param merge how many adjacent bins to merge into one.
253 inline reduce_command crop_and_rebin(unsigned iaxis, double lower, double upper,
255 reduce_command r = crop(iaxis, lower, upper);
256 r.merge = rebin(merge).merge;
260 /** Crop and rebin command to be used in `reduce`.
262 Command is applied to corresponding axis in order of reduce arguments.
264 To `crop` and `rebin` in one command (see the respective commands for more
265 details). Equivalent to passing both commands for the same axis to `reduce`.
267 @param lower lowest bound that should be kept.
268 @param upper highest bound that should be kept. If upper is inside bin interval,
269 the whole interval is removed.
270 @param merge how many adjacent bins to merge into one.
272 inline reduce_command crop_and_rebin(double lower, double upper, unsigned merge) {
273 return crop_and_rebin(reduce_command::unset, lower, upper, merge);
276 /** Slice and rebin command to be used in `reduce`.
278 Command is applied to axis with given index.
280 To `slice` and `rebin` in one command (see the respective commands for more
281 details). Equivalent to passing both commands for the same axis to `reduce`.
283 @param iaxis which axis to operate on.
284 @param begin first index that should be kept.
285 @param end one past the last index that should be kept.
286 @param merge how many adjacent bins to merge into one.
287 @param mode slice mode, see slice_mode.
289 inline reduce_command slice_and_rebin(unsigned iaxis, axis::index_type begin,
290 axis::index_type end, unsigned merge,
291 slice_mode mode = slice_mode::shrink) {
292 reduce_command r = slice(iaxis, begin, end, mode);
293 r.merge = rebin(merge).merge;
297 /** Slice and rebin command to be used in `reduce`.
299 Command is applied to corresponding axis in order of reduce arguments.
301 To `slice` and `rebin` in one command (see the respective commands for more
302 details). Equivalent to passing both commands for the same axis to `reduce`.
304 @param begin first index that should be kept.
305 @param end one past the last index that should be kept.
306 @param merge how many adjacent bins to merge into one.
307 @param mode slice mode, see slice_mode.
309 inline reduce_command slice_and_rebin(axis::index_type begin, axis::index_type end,
311 slice_mode mode = slice_mode::shrink) {
312 return slice_and_rebin(reduce_command::unset, begin, end, merge, mode);
315 /** Shrink, crop, slice, and/or rebin axes of a histogram.
317 Returns a new reduced histogram and leaves the original histogram untouched.
319 The commands `rebin` and `shrink` or `slice` for the same axis are
320 automatically combined, this is not an error. Passing a `shrink` and a `slice`
321 command for the same axis or two `rebin` commands triggers an `invalid_argument`
322 exception. Trying to reducing a non-reducible axis triggers an `invalid_argument`
323 exception. Histograms with non-reducible axes can still be reduced along the
324 other axes that are reducible.
326 @param hist original histogram.
327 @param options iterable sequence of reduce commands: `shrink`, `slice`, `rebin`,
328 `shrink_and_rebin`, or `slice_and_rebin`. The element type of the iterable should be
331 template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
332 Histogram reduce(const Histogram& hist, const Iterable& options) {
333 using axis::index_type;
335 const auto& old_axes = unsafe_access::axes(hist);
336 auto opts = detail::make_stack_buffer(old_axes, reduce_command{});
337 detail::normalize_reduce_commands(opts, options);
340 detail::axes_transform(old_axes, [&opts](std::size_t iaxis, const auto& a_in) {
341 using A = std::decay_t<decltype(a_in)>;
342 using AO = axis::traits::get_options<A>;
343 auto& o = opts[iaxis];
344 o.is_ordered = axis::traits::ordered(a_in);
345 if (o.merge > 0) { // option is set?
346 o.use_underflow_bin = !o.crop && AO::test(axis::option::underflow);
347 o.use_overflow_bin = !o.crop && AO::test(axis::option::overflow);
348 return detail::static_if_c<axis::traits::is_reducible<A>::value>(
349 [&o](const auto& a_in) {
350 if (o.range == reduce_command::range_t::none) {
352 o.end.index = a_in.size();
354 if (o.range == reduce_command::range_t::values) {
355 const auto end_value = o.end.value;
356 o.begin.index = axis::traits::index(a_in, o.begin.value);
357 o.end.index = axis::traits::index(a_in, o.end.value);
358 // end = index + 1, unless end_value equal to upper bin edge
359 if (axis::traits::value_as<double>(a_in, o.end.index) != end_value)
362 // limit [begin, end] to [0, size()]
363 if (o.begin.index < 0) o.begin.index = 0;
364 if (o.end.index > a_in.size()) o.end.index = a_in.size();
366 // shorten the index range to a multiple of o.merge;
367 // example [1, 4] with merge = 2 is reduced to [1, 3]
369 (o.end.index - o.begin.index) % static_cast<index_type>(o.merge);
370 using A = std::decay_t<decltype(a_in)>;
371 return A(a_in, o.begin.index, o.end.index, o.merge);
373 [iaxis](const auto& a_in) {
374 return BOOST_THROW_EXCEPTION(std::invalid_argument(
375 "axis " + std::to_string(iaxis) + " is not reducible")),
380 // command was not set for this axis; fill noop values and copy original axis
381 o.use_underflow_bin = AO::test(axis::option::underflow);
382 o.use_overflow_bin = AO::test(axis::option::overflow);
385 o.end.index = a_in.size();
391 Histogram(std::move(axes), detail::make_default(unsafe_access::storage(hist)));
393 auto idx = detail::make_stack_buffer<index_type>(unsafe_access::axes(result));
394 for (auto&& x : indexed(hist, coverage::all)) {
395 auto i = idx.begin();
396 auto o = opts.begin();
399 for (auto j : x.indices()) {
400 *i = (j - o->begin.index);
401 if (o->is_ordered && *i <= -1) {
403 if (!o->use_underflow_bin) skip = true;
406 *i /= static_cast<index_type>(o->merge);
409 const auto reduced_axis_end =
410 (o->end.index - o->begin.index) / static_cast<index_type>(o->merge);
411 if (*i >= reduced_axis_end) {
412 *i = reduced_axis_end;
413 if (!o->use_overflow_bin) skip = true;
421 if (!skip) result.at(idx) += *x;
427 /** Shrink, slice, and/or rebin axes of a histogram.
429 Returns a new reduced histogram and leaves the original histogram untouched.
431 The commands `rebin` and `shrink` or `slice` for the same axis are
432 automatically combined, this is not an error. Passing a `shrink` and a `slice`
433 command for the same axis or two `rebin` commands triggers an invalid_argument
434 exception. It is safe to reduce histograms with some axis that are not reducible along
435 the other axes. Trying to reducing a non-reducible axis triggers an invalid_argument
438 @param hist original histogram.
439 @param opt first reduce command; one of `shrink`, `slice`, `rebin`,
440 `shrink_and_rebin`, or `slice_or_rebin`.
441 @param opts more reduce commands.
443 template <class Histogram, class... Ts>
444 Histogram reduce(const Histogram& hist, const reduce_command& opt, const Ts&... opts) {
445 // this must be in one line, because any of the ts could be a temporary
446 return reduce(hist, std::initializer_list<reduce_command>{opt, opts...});
449 } // namespace algorithm
450 } // namespace histogram