fill.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. // Copyright 2015-2018 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_DETAIL_FILL_HPP
  7. #define BOOST_HISTOGRAM_DETAIL_FILL_HPP
  8. #include <algorithm>
  9. #include <boost/assert.hpp>
  10. #include <boost/config/workaround.hpp>
  11. #include <boost/histogram/axis/traits.hpp>
  12. #include <boost/histogram/axis/variant.hpp>
  13. #include <boost/histogram/detail/accumulator_traits.hpp>
  14. #include <boost/histogram/detail/argument_traits.hpp>
  15. #include <boost/histogram/detail/axes.hpp>
  16. #include <boost/histogram/detail/linearize.hpp>
  17. #include <boost/histogram/detail/make_default.hpp>
  18. #include <boost/histogram/detail/optional_index.hpp>
  19. #include <boost/histogram/detail/tuple_slice.hpp>
  20. #include <boost/histogram/fwd.hpp>
  21. #include <boost/mp11/algorithm.hpp>
  22. #include <boost/mp11/integral.hpp>
  23. #include <boost/mp11/tuple.hpp>
  24. #include <boost/mp11/utility.hpp>
  25. #include <mutex>
  26. #include <tuple>
  27. #include <type_traits>
  28. namespace boost {
  29. namespace histogram {
  30. namespace detail {
  31. template <class T, class U>
  32. struct sample_args_passed_vs_expected;
  33. template <class... Passed, class... Expected>
  34. struct sample_args_passed_vs_expected<std::tuple<Passed...>, std::tuple<Expected...>> {
  35. static_assert(!(sizeof...(Expected) > 0 && sizeof...(Passed) == 0),
  36. "error: accumulator requires samples, but sample argument is missing");
  37. static_assert(
  38. !(sizeof...(Passed) > 0 && sizeof...(Expected) == 0),
  39. "error: accumulator does not accept samples, but sample argument is passed");
  40. static_assert(sizeof...(Passed) == sizeof...(Expected),
  41. "error: numbers of passed and expected sample arguments differ");
  42. static_assert(
  43. std::is_convertible<std::tuple<Passed...>, std::tuple<Expected...>>::value,
  44. "error: sample argument(s) not convertible to accumulator argument(s)");
  45. };
  46. template <class A>
  47. struct storage_grower {
  48. const A& axes_;
  49. struct {
  50. axis::index_type idx, old_extent;
  51. std::size_t new_stride;
  52. } data_[buffer_size<A>::value];
  53. std::size_t new_size_;
  54. storage_grower(const A& axes) noexcept : axes_(axes) {}
  55. void from_shifts(const axis::index_type* shifts) noexcept {
  56. auto dit = data_;
  57. std::size_t s = 1;
  58. for_each_axis(axes_, [&](const auto& a) {
  59. const auto n = axis::traits::extent(a);
  60. *dit++ = {0, n - std::abs(*shifts++), s};
  61. s *= n;
  62. });
  63. new_size_ = s;
  64. }
  65. // must be extents before any shifts were applied
  66. void from_extents(const axis::index_type* old_extents) noexcept {
  67. auto dit = data_;
  68. std::size_t s = 1;
  69. for_each_axis(axes_, [&](const auto& a) {
  70. const auto n = axis::traits::extent(a);
  71. *dit++ = {0, *old_extents++, s};
  72. s *= n;
  73. });
  74. new_size_ = s;
  75. }
  76. template <class S>
  77. void apply(S& storage, const axis::index_type* shifts) {
  78. auto new_storage = make_default(storage);
  79. new_storage.reset(new_size_);
  80. const auto dlast = data_ + axes_rank(axes_) - 1;
  81. for (const auto& x : storage) {
  82. auto ns = new_storage.begin();
  83. auto sit = shifts;
  84. auto dit = data_;
  85. for_each_axis(axes_, [&](const auto& a) {
  86. using opt = axis::traits::static_options<std::decay_t<decltype(a)>>;
  87. if (opt::test(axis::option::underflow)) {
  88. if (dit->idx == 0) {
  89. // axis has underflow and we are in the underflow bin:
  90. // keep storage pointer unchanged
  91. ++dit;
  92. ++sit;
  93. return;
  94. }
  95. }
  96. if (opt::test(axis::option::overflow)) {
  97. if (dit->idx == dit->old_extent - 1) {
  98. // axis has overflow and we are in the overflow bin:
  99. // move storage pointer to corresponding overflow bin position
  100. ns += (axis::traits::extent(a) - 1) * dit->new_stride;
  101. ++dit;
  102. ++sit;
  103. return;
  104. }
  105. }
  106. // we are in a normal bin:
  107. // move storage pointer to index position, apply positive shifts
  108. ns += (dit->idx + std::max(*sit, 0)) * dit->new_stride;
  109. ++dit;
  110. ++sit;
  111. });
  112. // assign old value to new location
  113. *ns = x;
  114. // advance multi-dimensional index
  115. dit = data_;
  116. ++dit->idx;
  117. while (dit != dlast && dit->idx == dit->old_extent) {
  118. dit->idx = 0;
  119. ++(++dit)->idx;
  120. }
  121. }
  122. storage = std::move(new_storage);
  123. }
  124. };
  125. template <class T, class... Us>
  126. void fill_storage_4(mp11::mp_false, T&& t, Us&&... args) noexcept {
  127. t(std::forward<Us>(args)...);
  128. }
  129. template <class T>
  130. void fill_storage_4(mp11::mp_true, T&& t) noexcept {
  131. ++t;
  132. }
  133. template <class T, class U>
  134. void fill_storage_4(mp11::mp_true, T&& t, U&& w) noexcept {
  135. t += w.value;
  136. }
  137. template <class T, class... Us>
  138. void fill_storage_3(T&& t, Us&&... args) noexcept {
  139. fill_storage_4(has_operator_preincrement<std::decay_t<T>>{}, std::forward<T>(t),
  140. std::forward<Us>(args)...);
  141. }
  142. template <class IW, class IS, class T, class U>
  143. void fill_storage_2(IW, IS, T&& t, U&& u) noexcept {
  144. mp11::tuple_apply(
  145. [&](auto&&... args) {
  146. fill_storage_3(std::forward<T>(t), std::get<IW::value>(u), args...);
  147. },
  148. std::get<IS::value>(u).value);
  149. }
  150. template <class IS, class T, class U>
  151. void fill_storage_2(mp11::mp_int<-1>, IS, T&& t, const U& u) noexcept {
  152. mp11::tuple_apply(
  153. [&](const auto&... args) { fill_storage_3(std::forward<T>(t), args...); },
  154. std::get<IS::value>(u).value);
  155. }
  156. template <class IW, class T, class U>
  157. void fill_storage_2(IW, mp11::mp_int<-1>, T&& t, const U& u) noexcept {
  158. fill_storage_3(std::forward<T>(t), std::get<IW::value>(u));
  159. }
  160. template <class T, class U>
  161. void fill_storage_2(mp11::mp_int<-1>, mp11::mp_int<-1>, T&& t, const U&) noexcept {
  162. fill_storage_3(std::forward<T>(t));
  163. }
  164. template <class IW, class IS, class Storage, class Index, class Args>
  165. auto fill_storage(IW, IS, Storage& s, const Index idx, const Args& a) noexcept {
  166. if (is_valid(idx)) {
  167. BOOST_ASSERT(idx < s.size());
  168. fill_storage_2(IW{}, IS{}, s[idx], a);
  169. return s.begin() + idx;
  170. }
  171. return s.end();
  172. }
  173. template <int S, int N>
  174. struct linearize_args {
  175. template <class Index, class A, class Args>
  176. static void impl(mp11::mp_int<N>, Index&, const std::size_t, A&, const Args&) {}
  177. template <int I, class Index, class A, class Args>
  178. static void impl(mp11::mp_int<I>, Index& o, const std::size_t s, A& ax,
  179. const Args& args) {
  180. const auto e = linearize(o, s, axis_get<I>(ax), std::get<(S + I)>(args));
  181. impl(mp11::mp_int<(I + 1)>{}, o, s * e, ax, args);
  182. }
  183. template <class Index, class A, class Args>
  184. static void apply(Index& o, A& ax, const Args& args) {
  185. impl(mp11::mp_int<0>{}, o, 1, ax, args);
  186. }
  187. };
  188. template <int S>
  189. struct linearize_args<S, 1> {
  190. template <class Index, class A, class Args>
  191. static void apply(Index& o, A& ax, const Args& args) {
  192. linearize(o, 1, axis_get<0>(ax), std::get<S>(args));
  193. }
  194. };
  195. template <class A>
  196. constexpr unsigned min(const unsigned n) noexcept {
  197. constexpr unsigned a = static_cast<unsigned>(buffer_size<A>::value);
  198. return a < n ? a : n;
  199. }
  200. // not growing
  201. template <class ArgTraits, class Storage, class Axes, class Args>
  202. auto fill_2(ArgTraits, mp11::mp_false, const std::size_t offset, Storage& st,
  203. const Axes& axes, const Args& args) {
  204. mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{offset};
  205. linearize_args<ArgTraits::start::value, min<Axes>(ArgTraits::nargs::value)>::apply(
  206. idx, axes, args);
  207. return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx,
  208. args);
  209. }
  210. // at least one axis is growing
  211. template <class ArgTraits, class Storage, class Axes, class Args>
  212. auto fill_2(ArgTraits, mp11::mp_true, const std::size_t, Storage& st, Axes& axes,
  213. const Args& args) {
  214. std::array<axis::index_type, ArgTraits::nargs::value> shifts;
  215. // offset must be zero for linearize_growth
  216. mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{0};
  217. std::size_t stride = 1;
  218. bool update_needed = false;
  219. mp11::mp_for_each<mp11::mp_iota_c<min<Axes>(ArgTraits::nargs::value)>>([&](auto i) {
  220. auto& ax = axis_get<i>(axes);
  221. const auto extent = linearize_growth(idx, shifts[i], stride, ax,
  222. std::get<(ArgTraits::start::value + i)>(args));
  223. update_needed |= shifts[i] != 0;
  224. stride *= extent;
  225. });
  226. if (update_needed) {
  227. storage_grower<Axes> g(axes);
  228. g.from_shifts(shifts.data());
  229. g.apply(st, shifts.data());
  230. }
  231. return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx,
  232. args);
  233. }
  234. // pack original args tuple into another tuple (which is unpacked later)
  235. template <int Start, int Size, class IW, class IS, class Args>
  236. decltype(auto) pack_args(IW, IS, const Args& args) noexcept {
  237. return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args),
  238. std::get<IS::value>(args));
  239. }
  240. template <int Start, int Size, class IW, class Args>
  241. decltype(auto) pack_args(IW, mp11::mp_int<-1>, const Args& args) noexcept {
  242. return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args));
  243. }
  244. template <int Start, int Size, class IS, class Args>
  245. decltype(auto) pack_args(mp11::mp_int<-1>, IS, const Args& args) noexcept {
  246. return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IS::value>(args));
  247. }
  248. template <int Start, int Size, class Args>
  249. decltype(auto) pack_args(mp11::mp_int<-1>, mp11::mp_int<-1>, const Args& args) noexcept {
  250. return std::make_tuple(args);
  251. }
  252. #if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
  253. #pragma warning(disable : 4702) // fixing warning would reduce code readability a lot
  254. #endif
  255. template <class ArgTraits, class S, class A, class Args>
  256. auto fill(std::true_type, ArgTraits, const std::size_t offset, S& storage, A& axes,
  257. const Args& args) {
  258. using growing = has_growing_axis<A>;
  259. // Sometimes we need to pack the tuple into another tuple:
  260. // - histogram contains one axis which accepts tuple
  261. // - user passes tuple to fill(...)
  262. // Tuple is normally unpacked and arguments are processed, this causes pos::nargs > 1.
  263. // Now we pack tuple into another tuple so that original tuple is send to axis.
  264. // Notes:
  265. // - has nice side-effect of making histogram::operator(1, 2) work as well
  266. // - cannot detect call signature of axis at compile-time in all configurations
  267. // (axis::variant provides generic call interface and hides concrete
  268. // interface), so we throw at runtime if incompatible argument is passed (e.g.
  269. // 3d tuple)
  270. if (axes_rank(axes) == ArgTraits::nargs::value)
  271. return fill_2(ArgTraits{}, growing{}, offset, storage, axes, args);
  272. else if (axes_rank(axes) == 1 &&
  273. axis::traits::rank(axis_get<0>(axes)) == ArgTraits::nargs::value)
  274. return fill_2(
  275. argument_traits_holder<
  276. 1, 0, (ArgTraits::wpos::value >= 0 ? 1 : -1),
  277. (ArgTraits::spos::value >= 0 ? (ArgTraits::wpos::value >= 0 ? 2 : 1) : -1),
  278. typename ArgTraits::sargs>{},
  279. growing{}, offset, storage, axes,
  280. pack_args<ArgTraits::start::value, ArgTraits::nargs::value>(
  281. typename ArgTraits::wpos{}, typename ArgTraits::spos{}, args));
  282. return (BOOST_THROW_EXCEPTION(
  283. std::invalid_argument("number of arguments != histogram rank")),
  284. storage.end());
  285. }
  286. #if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
  287. #pragma warning(default : 4702)
  288. #endif
  289. // empty implementation for bad arguments to stop compiler from showing internals
  290. template <class ArgTraits, class S, class A, class Args>
  291. auto fill(std::false_type, ArgTraits, const std::size_t, S& storage, A&, const Args&) {
  292. return storage.end();
  293. }
  294. } // namespace detail
  295. } // namespace histogram
  296. } // namespace boost
  297. #endif