reduce.hpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. // Copyright 2018-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. #ifndef BOOST_HISTOGRAM_ALGORITHM_REDUCE_HPP
  7. #define BOOST_HISTOGRAM_ALGORITHM_REDUCE_HPP
  8. #include <boost/histogram/axis/traits.hpp>
  9. #include <boost/histogram/detail/axes.hpp>
  10. #include <boost/histogram/detail/make_default.hpp>
  11. #include <boost/histogram/detail/reduce_command.hpp>
  12. #include <boost/histogram/detail/static_if.hpp>
  13. #include <boost/histogram/fwd.hpp>
  14. #include <boost/histogram/indexed.hpp>
  15. #include <boost/histogram/unsafe_access.hpp>
  16. #include <boost/throw_exception.hpp>
  17. #include <cassert>
  18. #include <cmath>
  19. #include <initializer_list>
  20. #include <stdexcept>
  21. #include <string>
  22. namespace boost {
  23. namespace histogram {
  24. namespace algorithm {
  25. /** Holder for a reduce command.
  26. Use this type to store reduce commands in a container. The internals of this type are an
  27. implementation detail.
  28. */
  29. using reduce_command = detail::reduce_command;
  30. using reduce_option [[deprecated("use reduce_command instead")]] =
  31. reduce_command; ///< deprecated
  32. /** Shrink command to be used in `reduce`.
  33. Command is applied to axis with given index.
  34. Shrinking is based on an inclusive value interval. The bin which contains the first
  35. value starts the range of bins to keep. The bin which contains the second value is the
  36. last included in that range. When the second value is exactly equal to a lower bin edge,
  37. then the previous bin is the last in the range.
  38. The counts in removed bins are added to the corresponding underflow and overflow bins,
  39. if they are present. If they are not present, the counts are discarded. Also see
  40. `crop`, which always discards the counts.
  41. @param iaxis which axis to operate on.
  42. @param lower bin which contains lower is first to be kept.
  43. @param upper bin which contains upper is last to be kept, except if upper is equal to
  44. the lower edge.
  45. */
  46. inline reduce_command shrink(unsigned iaxis, double lower, double upper) {
  47. if (lower == upper)
  48. BOOST_THROW_EXCEPTION(std::invalid_argument("lower != upper required"));
  49. reduce_command r;
  50. r.iaxis = iaxis;
  51. r.range = reduce_command::range_t::values;
  52. r.begin.value = lower;
  53. r.end.value = upper;
  54. r.merge = 1;
  55. r.crop = false;
  56. return r;
  57. }
  58. /** Shrink command to be used in `reduce`.
  59. Command is applied to corresponding axis in order of reduce arguments.
  60. Shrinking is based on an inclusive value interval. The bin which contains the first
  61. value starts the range of bins to keep. The bin which contains the second value is the
  62. last included in that range. When the second value is exactly equal to a lower bin edge,
  63. then the previous bin is the last in the range.
  64. The counts in removed bins are added to the corresponding underflow and overflow bins,
  65. if they are present. If they are not present, the counts are discarded. Also see
  66. `crop`, which always discards the counts.
  67. @param lower bin which contains lower is first to be kept.
  68. @param upper bin which contains upper is last to be kept, except if upper is equal to
  69. the lower edge.
  70. */
  71. inline reduce_command shrink(double lower, double upper) {
  72. return shrink(reduce_command::unset, lower, upper);
  73. }
  74. /** Crop command to be used in `reduce`.
  75. Command is applied to axis with given index.
  76. Works like `shrink` (see shrink documentation for details), but counts in removed
  77. bins are always discarded, whether underflow and overflow bins are present or not.
  78. @param iaxis which axis to operate on.
  79. @param lower bin which contains lower is first to be kept.
  80. @param upper bin which contains upper is last to be kept, except if upper is equal to
  81. the lower edge.
  82. */
  83. inline reduce_command crop(unsigned iaxis, double lower, double upper) {
  84. reduce_command r = shrink(iaxis, lower, upper);
  85. r.crop = true;
  86. return r;
  87. }
  88. /** Crop command to be used in `reduce`.
  89. Command is applied to corresponding axis in order of reduce arguments.
  90. Works like `shrink` (see shrink documentation for details), but counts in removed bins
  91. are discarded, whether underflow and overflow bins are present or not. If the cropped
  92. range goes beyond the axis range, then the content of the underflow
  93. or overflow bin which overlaps with the range is kept.
  94. If the counts in an existing underflow or overflow bin are discared by the crop, the
  95. corresponding memory cells are not physically removed. Only their contents are set to
  96. zero. This technical limitation may be lifted in the future, then crop may completely
  97. remove the cropped memory cells.
  98. @param lower bin which contains lower is first to be kept.
  99. @param upper bin which contains upper is last to be kept, except if upper is equal to
  100. the lower edge.
  101. */
  102. inline reduce_command crop(double lower, double upper) {
  103. return crop(reduce_command::unset, lower, upper);
  104. }
  105. /// Whether to behave like `shrink` or `crop` regarding removed bins.
  106. enum class slice_mode { shrink, crop };
  107. /** Slice command to be used in `reduce`.
  108. Command is applied to axis with given index.
  109. Slicing works like `shrink` or `crop`, but uses bin indices instead of values.
  110. @param iaxis which axis to operate on.
  111. @param begin first index that should be kept.
  112. @param end one past the last index that should be kept.
  113. @param mode whether to behave like `shrink` or `crop` regarding removed bins.
  114. */
  115. inline reduce_command slice(unsigned iaxis, axis::index_type begin, axis::index_type end,
  116. slice_mode mode = slice_mode::shrink) {
  117. if (!(begin < end))
  118. BOOST_THROW_EXCEPTION(std::invalid_argument("begin < end required"));
  119. reduce_command r;
  120. r.iaxis = iaxis;
  121. r.range = reduce_command::range_t::indices;
  122. r.begin.index = begin;
  123. r.end.index = end;
  124. r.merge = 1;
  125. r.crop = mode == slice_mode::crop;
  126. return r;
  127. }
  128. /** Slice command to be used in `reduce`.
  129. Command is applied to corresponding axis in order of reduce arguments.
  130. Slicing works like `shrink` or `crop`, but uses bin indices instead of values.
  131. @param begin first index that should be kept.
  132. @param end one past the last index that should be kept.
  133. @param mode whether to behave like `shrink` or `crop` regarding removed bins.
  134. */
  135. inline reduce_command slice(axis::index_type begin, axis::index_type end,
  136. slice_mode mode = slice_mode::shrink) {
  137. return slice(reduce_command::unset, begin, end, mode);
  138. }
  139. /** Rebin command to be used in `reduce`.
  140. Command is applied to axis with given index.
  141. The command merges N adjacent bins into one. This makes the axis coarser and the bins
  142. wider. The original number of bins is divided by N. If there is a rest to this devision,
  143. the axis is implicitly shrunk at the upper end by that rest.
  144. @param iaxis which axis to operate on.
  145. @param merge how many adjacent bins to merge into one.
  146. */
  147. inline reduce_command rebin(unsigned iaxis, unsigned merge) {
  148. if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
  149. reduce_command r;
  150. r.iaxis = iaxis;
  151. r.merge = merge;
  152. r.range = reduce_command::range_t::none;
  153. r.crop = false;
  154. return r;
  155. }
  156. /** Rebin command to be used in `reduce`.
  157. Command is applied to corresponding axis in order of reduce arguments.
  158. The command merges N adjacent bins into one. This makes the axis coarser and the bins
  159. wider. The original number of bins is divided by N. If there is a rest to this devision,
  160. the axis is implicitly shrunk at the upper end by that rest.
  161. @param merge how many adjacent bins to merge into one.
  162. */
  163. inline reduce_command rebin(unsigned merge) {
  164. return rebin(reduce_command::unset, merge);
  165. }
  166. /** Shrink and rebin command to be used in `reduce`.
  167. Command is applied to corresponding axis in order of reduce arguments.
  168. To shrink(unsigned, double, double) and rebin(unsigned, unsigned) in one command (see
  169. the respective commands for more details). Equivalent to passing both commands for the
  170. same axis to `reduce`.
  171. @param iaxis which axis to operate on.
  172. @param lower lowest bound that should be kept.
  173. @param upper highest bound that should be kept. If upper is inside bin interval, the
  174. whole interval is removed.
  175. @param merge how many adjacent bins to merge into one.
  176. */
  177. inline reduce_command shrink_and_rebin(unsigned iaxis, double lower, double upper,
  178. unsigned merge) {
  179. reduce_command r = shrink(iaxis, lower, upper);
  180. r.merge = rebin(merge).merge;
  181. return r;
  182. }
  183. /** Shrink and rebin command to be used in `reduce`.
  184. Command is applied to corresponding axis in order of reduce arguments.
  185. To `shrink` and `rebin` in one command (see the respective commands for more
  186. details). Equivalent to passing both commands for the same axis to `reduce`.
  187. @param lower lowest bound that should be kept.
  188. @param upper highest bound that should be kept. If upper is inside bin interval, the
  189. whole interval is removed.
  190. @param merge how many adjacent bins to merge into one.
  191. */
  192. inline reduce_command shrink_and_rebin(double lower, double upper, unsigned merge) {
  193. return shrink_and_rebin(reduce_command::unset, lower, upper, merge);
  194. }
  195. /** Crop and rebin command to be used in `reduce`.
  196. Command is applied to axis with given index.
  197. To `crop` and `rebin` in one command (see the respective commands for more
  198. details). Equivalent to passing both commands for the same axis to `reduce`.
  199. @param iaxis which axis to operate on.
  200. @param lower lowest bound that should be kept.
  201. @param upper highest bound that should be kept. If upper is inside bin interval,
  202. the whole interval is removed.
  203. @param merge how many adjacent bins to merge into one.
  204. */
  205. inline reduce_command crop_and_rebin(unsigned iaxis, double lower, double upper,
  206. unsigned merge) {
  207. reduce_command r = crop(iaxis, lower, upper);
  208. r.merge = rebin(merge).merge;
  209. return r;
  210. }
  211. /** Crop and rebin command to be used in `reduce`.
  212. Command is applied to corresponding axis in order of reduce arguments.
  213. To `crop` and `rebin` in one command (see the respective commands for more
  214. details). Equivalent to passing both commands for the same axis to `reduce`.
  215. @param lower lowest bound that should be kept.
  216. @param upper highest bound that should be kept. If upper is inside bin interval,
  217. the whole interval is removed.
  218. @param merge how many adjacent bins to merge into one.
  219. */
  220. inline reduce_command crop_and_rebin(double lower, double upper, unsigned merge) {
  221. return crop_and_rebin(reduce_command::unset, lower, upper, merge);
  222. }
  223. /** Slice and rebin command to be used in `reduce`.
  224. Command is applied to axis with given index.
  225. To `slice` and `rebin` in one command (see the respective commands for more
  226. details). Equivalent to passing both commands for the same axis to `reduce`.
  227. @param iaxis which axis to operate on.
  228. @param begin first index that should be kept.
  229. @param end one past the last index that should be kept.
  230. @param merge how many adjacent bins to merge into one.
  231. @param mode slice mode, see slice_mode.
  232. */
  233. inline reduce_command slice_and_rebin(unsigned iaxis, axis::index_type begin,
  234. axis::index_type end, unsigned merge,
  235. slice_mode mode = slice_mode::shrink) {
  236. reduce_command r = slice(iaxis, begin, end, mode);
  237. r.merge = rebin(merge).merge;
  238. return r;
  239. }
  240. /** Slice and rebin command to be used in `reduce`.
  241. Command is applied to corresponding axis in order of reduce arguments.
  242. To `slice` and `rebin` in one command (see the respective commands for more
  243. details). Equivalent to passing both commands for the same axis to `reduce`.
  244. @param begin first index that should be kept.
  245. @param end one past the last index that should be kept.
  246. @param merge how many adjacent bins to merge into one.
  247. @param mode slice mode, see slice_mode.
  248. */
  249. inline reduce_command slice_and_rebin(axis::index_type begin, axis::index_type end,
  250. unsigned merge,
  251. slice_mode mode = slice_mode::shrink) {
  252. return slice_and_rebin(reduce_command::unset, begin, end, merge, mode);
  253. }
  254. /** Shrink, crop, slice, and/or rebin axes of a histogram.
  255. Returns a new reduced histogram and leaves the original histogram untouched.
  256. The commands `rebin` and `shrink` or `slice` for the same axis are
  257. automatically combined, this is not an error. Passing a `shrink` and a `slice`
  258. command for the same axis or two `rebin` commands triggers an `invalid_argument`
  259. exception. Trying to reducing a non-reducible axis triggers an `invalid_argument`
  260. exception. Histograms with non-reducible axes can still be reduced along the
  261. other axes that are reducible.
  262. An overload allows one to pass reduce_command as positional arguments.
  263. @param hist original histogram.
  264. @param options iterable sequence of reduce commands: `shrink`, `slice`, `rebin`,
  265. `shrink_and_rebin`, or `slice_and_rebin`. The element type of the iterable should be
  266. `reduce_command`.
  267. */
  268. template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
  269. Histogram reduce(const Histogram& hist, const Iterable& options) {
  270. using axis::index_type;
  271. const auto& old_axes = unsafe_access::axes(hist);
  272. auto opts = detail::make_stack_buffer(old_axes, reduce_command{});
  273. detail::normalize_reduce_commands(opts, options);
  274. auto axes =
  275. detail::axes_transform(old_axes, [&opts](std::size_t iaxis, const auto& a_in) {
  276. using A = std::decay_t<decltype(a_in)>;
  277. using AO = axis::traits::get_options<A>;
  278. auto& o = opts[iaxis];
  279. o.is_ordered = axis::traits::ordered(a_in);
  280. if (o.merge > 0) { // option is set?
  281. o.use_underflow_bin = AO::test(axis::option::underflow);
  282. o.use_overflow_bin = AO::test(axis::option::overflow);
  283. return detail::static_if_c<axis::traits::is_reducible<A>::value>(
  284. [&o](const auto& a_in) {
  285. if (o.range == reduce_command::range_t::none) {
  286. // no range restriction, pure rebin
  287. o.begin.index = 0;
  288. o.end.index = a_in.size();
  289. } else {
  290. // range striction, convert values to indices as needed
  291. if (o.range == reduce_command::range_t::values) {
  292. const auto end_value = o.end.value;
  293. o.begin.index = axis::traits::index(a_in, o.begin.value);
  294. o.end.index = axis::traits::index(a_in, o.end.value);
  295. // end = index + 1, unless end_value equal to upper bin edge
  296. if (axis::traits::value_as<double>(a_in, o.end.index) != end_value)
  297. ++o.end.index;
  298. }
  299. // crop flow bins if index range does not include them
  300. if (o.crop) {
  301. o.use_underflow_bin &= o.begin.index < 0;
  302. o.use_overflow_bin &= o.end.index > a_in.size();
  303. }
  304. // now limit [begin, end] to [0, size()]
  305. if (o.begin.index < 0) o.begin.index = 0;
  306. if (o.end.index > a_in.size()) o.end.index = a_in.size();
  307. }
  308. // shorten the index range to a multiple of o.merge;
  309. // example [1, 4] with merge = 2 is reduced to [1, 3]
  310. o.end.index -=
  311. (o.end.index - o.begin.index) % static_cast<index_type>(o.merge);
  312. using A = std::decay_t<decltype(a_in)>;
  313. return A(a_in, o.begin.index, o.end.index, o.merge);
  314. },
  315. [iaxis](const auto& a_in) {
  316. return BOOST_THROW_EXCEPTION(std::invalid_argument(
  317. "axis " + std::to_string(iaxis) + " is not reducible")),
  318. a_in;
  319. },
  320. a_in);
  321. } else {
  322. // command was not set for this axis; fill noop values and copy original axis
  323. o.use_underflow_bin = AO::test(axis::option::underflow);
  324. o.use_overflow_bin = AO::test(axis::option::overflow);
  325. o.merge = 1;
  326. o.begin.index = 0;
  327. o.end.index = a_in.size();
  328. return a_in;
  329. }
  330. });
  331. auto result =
  332. Histogram(std::move(axes), detail::make_default(unsafe_access::storage(hist)));
  333. auto idx = detail::make_stack_buffer<index_type>(unsafe_access::axes(result));
  334. for (auto&& x : indexed(hist, coverage::all)) {
  335. auto i = idx.begin();
  336. auto o = opts.begin();
  337. bool skip = false;
  338. for (auto j : x.indices()) {
  339. *i = (j - o->begin.index);
  340. if (o->is_ordered && *i <= -1) {
  341. *i = -1;
  342. if (!o->use_underflow_bin) skip = true;
  343. } else {
  344. if (*i >= 0)
  345. *i /= static_cast<index_type>(o->merge);
  346. else
  347. *i = o->end.index;
  348. const auto reduced_axis_end =
  349. (o->end.index - o->begin.index) / static_cast<index_type>(o->merge);
  350. if (*i >= reduced_axis_end) {
  351. *i = reduced_axis_end;
  352. if (!o->use_overflow_bin) skip = true;
  353. }
  354. }
  355. ++i;
  356. ++o;
  357. }
  358. if (!skip) result.at(idx) += *x;
  359. }
  360. return result;
  361. }
  362. /** Shrink, slice, and/or rebin axes of a histogram.
  363. Returns a new reduced histogram and leaves the original histogram untouched.
  364. The commands `rebin` and `shrink` or `slice` for the same axis are
  365. automatically combined, this is not an error. Passing a `shrink` and a `slice`
  366. command for the same axis or two `rebin` commands triggers an invalid_argument
  367. exception. It is safe to reduce histograms with some axis that are not reducible along
  368. the other axes. Trying to reducing a non-reducible axis triggers an invalid_argument
  369. exception.
  370. An overload allows one to pass an iterable of reduce_command.
  371. @param hist original histogram.
  372. @param opt first reduce command; one of `shrink`, `slice`, `rebin`,
  373. `shrink_and_rebin`, or `slice_or_rebin`.
  374. @param opts more reduce commands.
  375. */
  376. template <class Histogram, class... Ts>
  377. Histogram reduce(const Histogram& hist, const reduce_command& opt, const Ts&... opts) {
  378. // this must be in one line, because any of the ts could be a temporary
  379. return reduce(hist, std::initializer_list<reduce_command>{opt, opts...});
  380. }
  381. } // namespace algorithm
  382. } // namespace histogram
  383. } // namespace boost
  384. #endif