5bd5bfb9.patch 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. From 5bd5bfb978df5f345518d2444d8f2ddba849d8d6 Mon Sep 17 00:00:00 2001
  2. From: Hans Dembinski <HDembinski@users.noreply.github.com>
  3. Date: Mon, 7 Dec 2020 15:12:20 +0100
  4. Subject: [PATCH] Bug-fix for one-sided cropping and clarify how cropping works
  5. (#302)
  6. ---
  7. boost/histogram/algorithm/reduce.hpp | 28 +++++++--
  8. libs/histogram/test/algorithm_reduce_test.cpp | 63 ++++++++++++++++----
  9. 2 files changed, 77 insertions(+), 14 deletions(-)
  10. diff --git a/include/boost/histogram/algorithm/reduce.hpp b/include/boost/histogram/algorithm/reduce.hpp
  11. index a5d2fffa6..0646db34e 100644
  12. --- a/boost/histogram/algorithm/reduce.hpp
  13. +++ b/boost/histogram/algorithm/reduce.hpp
  14. @@ -111,7 +111,14 @@ inline reduce_command crop(unsigned iaxis, double lower, double upper) {
  15. Command is applied to corresponding axis in order of reduce arguments.
  16. Works like `shrink` (see shrink documentation for details), but counts in removed bins
  17. - are discarded, whether underflow and overflow bins are present or not.
  18. + are discarded, whether underflow and overflow bins are present or not. If the cropped
  19. + range goes beyond the axis range, then the content of the underflow
  20. + or overflow bin which overlaps with the range is kept.
  21. +
  22. + If the counts in an existing underflow or overflow bin are discared by the crop, the
  23. + corresponding memory cells are not physically removed. Only their contents are set to
  24. + zero. This technical limitation may be lifted in the future, then crop may completely
  25. + remove the cropped memory cells.
  26. @param lower bin which contains lower is first to be kept.
  27. @param upper bin which contains upper is last to be kept, except if upper is equal to
  28. @@ -323,6 +330,8 @@ inline reduce_command slice_and_rebin(axis::index_type begin, axis::index_type e
  29. exception. Histograms with non-reducible axes can still be reduced along the
  30. other axes that are reducible.
  31. + An overload allows one to pass reduce_command as positional arguments.
  32. +
  33. @param hist original histogram.
  34. @param options iterable sequence of reduce commands: `shrink`, `slice`, `rebin`,
  35. `shrink_and_rebin`, or `slice_and_rebin`. The element type of the iterable should be
  36. @@ -343,14 +352,16 @@ Histogram reduce(const Histogram& hist, const Iterable& options) {
  37. auto& o = opts[iaxis];
  38. o.is_ordered = axis::traits::ordered(a_in);
  39. if (o.merge > 0) { // option is set?
  40. - o.use_underflow_bin = !o.crop && AO::test(axis::option::underflow);
  41. - o.use_overflow_bin = !o.crop && AO::test(axis::option::overflow);
  42. + o.use_underflow_bin = AO::test(axis::option::underflow);
  43. + o.use_overflow_bin = AO::test(axis::option::overflow);
  44. return detail::static_if_c<axis::traits::is_reducible<A>::value>(
  45. [&o](const auto& a_in) {
  46. if (o.range == reduce_command::range_t::none) {
  47. + // no range restriction, pure rebin
  48. o.begin.index = 0;
  49. o.end.index = a_in.size();
  50. } else {
  51. + // range striction, convert values to indices as needed
  52. if (o.range == reduce_command::range_t::values) {
  53. const auto end_value = o.end.value;
  54. o.begin.index = axis::traits::index(a_in, o.begin.value);
  55. @@ -359,7 +370,14 @@ Histogram reduce(const Histogram& hist, const Iterable& options) {
  56. if (axis::traits::value_as<double>(a_in, o.end.index) != end_value)
  57. ++o.end.index;
  58. }
  59. - // limit [begin, end] to [0, size()]
  60. +
  61. + // crop flow bins if index range does not include them
  62. + if (o.crop) {
  63. + o.use_underflow_bin &= o.begin.index < 0;
  64. + o.use_overflow_bin &= o.end.index > a_in.size();
  65. + }
  66. +
  67. + // now limit [begin, end] to [0, size()]
  68. if (o.begin.index < 0) o.begin.index = 0;
  69. if (o.end.index > a_in.size()) o.end.index = a_in.size();
  70. }
  71. @@ -435,6 +453,8 @@ Histogram reduce(const Histogram& hist, const Iterable& options) {
  72. the other axes. Trying to reducing a non-reducible axis triggers an invalid_argument
  73. exception.
  74. + An overload allows one to pass an iterable of reduce_command.
  75. +
  76. @param hist original histogram.
  77. @param opt first reduce command; one of `shrink`, `slice`, `rebin`,
  78. `shrink_and_rebin`, or `slice_or_rebin`.
  79. diff --git a/test/algorithm_reduce_test.cpp b/test/algorithm_reduce_test.cpp
  80. index 6ea42fadf..c6b4df4a4 100644
  81. --- a/libs/histogram/test/algorithm_reduce_test.cpp
  82. +++ b/libs/histogram/test/algorithm_reduce_test.cpp
  83. @@ -105,15 +105,16 @@ void run_tests() {
  84. BOOST_TEST_EQ(reduce(h, crop(0, 1.999)).axis(), ID(0, 2));
  85. }
  86. + // shrink and rebin
  87. {
  88. auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5, "1"), R(3, -1, 2, "2"));
  89. /*
  90. matrix layout:
  91. - x
  92. + x ->
  93. y 1 0 1 0
  94. - 1 1 0 0
  95. - 0 2 1 3
  96. + | 1 1 0 0
  97. + v 0 2 1 3
  98. */
  99. h.at(0, 0) = 1;
  100. h.at(0, 1) = 1;
  101. @@ -122,11 +123,13 @@ void run_tests() {
  102. h.at(2, 0) = 1;
  103. h.at(2, 2) = 1;
  104. h.at(3, 2) = 3;
  105. + h.at(-1, -1) = 1; // underflow
  106. + h.at(4, 3) = 1; // overflow
  107. // should do nothing, index order does not matter
  108. auto hr = reduce(h, shrink(1, -1, 2), rebin(0, 1));
  109. BOOST_TEST_EQ(hr.rank(), 2);
  110. - BOOST_TEST_EQ(sum(hr), 10);
  111. + BOOST_TEST_EQ(sum(hr), 12);
  112. BOOST_TEST_EQ(hr.axis(0), R(4, 1, 5, "1"));
  113. BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
  114. BOOST_TEST_EQ(hr, h);
  115. @@ -138,7 +141,7 @@ void run_tests() {
  116. // shrinking along first axis
  117. hr = reduce(h, shrink(0, 2, 4));
  118. BOOST_TEST_EQ(hr.rank(), 2);
  119. - BOOST_TEST_EQ(sum(hr), 10);
  120. + BOOST_TEST_EQ(sum(hr), 12);
  121. BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4, "1"));
  122. BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
  123. BOOST_TEST_EQ(hr.at(-1, 0), 1); // underflow
  124. @@ -164,7 +167,7 @@ void run_tests() {
  125. hr = reduce(h, shrink_and_rebin(0, 2, 5, 2), rebin(1, 3));
  126. BOOST_TEST_EQ(hr.rank(), 2);
  127. - BOOST_TEST_EQ(sum(hr), 10);
  128. + BOOST_TEST_EQ(sum(hr), 12);
  129. BOOST_TEST_EQ(hr.axis(0), R(1, 2, 4, "1"));
  130. BOOST_TEST_EQ(hr.axis(1), R(1, -1, 2, "2"));
  131. BOOST_TEST_EQ(hr.at(-1, 0), 2); // underflow
  132. @@ -184,16 +187,16 @@ void run_tests() {
  133. BOOST_TEST_EQ(hr4, hr);
  134. }
  135. - // crop
  136. + // crop and rebin
  137. {
  138. auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5), R(3, 1, 4));
  139. /*
  140. matrix layout:
  141. - x
  142. + x ->
  143. y 1 0 1 0
  144. - 1 1 0 0
  145. - 0 2 1 3
  146. + | 1 1 0 0
  147. + v 0 2 1 3
  148. */
  149. h.at(0, 0) = 1;
  150. h.at(0, 1) = 1;
  151. @@ -202,6 +205,9 @@ void run_tests() {
  152. h.at(2, 0) = 1;
  153. h.at(2, 2) = 1;
  154. h.at(3, 2) = 3;
  155. + h.at(-1, -1) = 1; // underflow
  156. + h.at(4, 3) = 1; // overflow
  157. +
  158. /*
  159. crop first and last column in x and y
  160. @@ -231,6 +237,43 @@ void run_tests() {
  161. BOOST_TEST_EQ(hr, hr4);
  162. }
  163. + // one-sided crop
  164. + {
  165. + auto h = make_s(Tag(), std::vector<int>(), ID(1, 4));
  166. + std::fill(h.begin(), h.end(), 1);
  167. + // underflow: 1
  168. + // index 0, x 1: 1
  169. + // index 1, x 2: 1
  170. + // index 2, x 3: 1
  171. + // overflow: 1
  172. + BOOST_TEST_EQ(sum(h), 5);
  173. + BOOST_TEST_EQ(h.size(), 5);
  174. +
  175. + // keep underflow
  176. + auto hr1 = reduce(h, crop(0, 3));
  177. + BOOST_TEST_EQ(hr1, reduce(h, slice(-1, 2, slice_mode::crop)));
  178. + BOOST_TEST_EQ(sum(hr1), 3);
  179. + BOOST_TEST_EQ(hr1.size(), 4); // flow bins are not physically removed, only zeroed
  180. +
  181. + // remove underflow
  182. + auto hr2 = reduce(h, crop(1, 3));
  183. + BOOST_TEST_EQ(hr2, reduce(h, slice(0, 2, slice_mode::crop)));
  184. + BOOST_TEST_EQ(sum(hr2), 2);
  185. + BOOST_TEST_EQ(hr2.size(), 4); // flow bins are not physically removed, only zeroed
  186. +
  187. + // keep overflow
  188. + auto hr3 = reduce(h, crop(2, 5));
  189. + BOOST_TEST_EQ(hr3, reduce(h, slice(1, 4, slice_mode::crop)));
  190. + BOOST_TEST_EQ(sum(hr3), 3);
  191. + BOOST_TEST_EQ(hr3.size(), 4); // flow bins are not physically removed, only zeroed
  192. +
  193. + // remove overflow
  194. + auto hr4 = reduce(h, crop(2, 4));
  195. + BOOST_TEST_EQ(hr4, reduce(h, slice(1, 3, slice_mode::crop)));
  196. + BOOST_TEST_EQ(sum(hr4), 2);
  197. + BOOST_TEST_EQ(hr4.size(), 4); // flow bins are not physically removed, only zeroed
  198. + }
  199. +
  200. // mixed axis types
  201. {
  202. R r(5, 0.0, 5.0);