| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 | // Copyright 2015-2018 Hans Dembinski//// Distributed under the Boost Software License, Version 1.0.// (See accompanying file LICENSE_1_0.txt// or copy at http://www.boost.org/LICENSE_1_0.txt)#ifndef BOOST_HISTOGRAM_DETAIL_FILL_HPP#define BOOST_HISTOGRAM_DETAIL_FILL_HPP#include <algorithm>#include <boost/assert.hpp>#include <boost/config/workaround.hpp>#include <boost/histogram/axis/traits.hpp>#include <boost/histogram/axis/variant.hpp>#include <boost/histogram/detail/argument_traits.hpp>#include <boost/histogram/detail/axes.hpp>#include <boost/histogram/detail/linearize.hpp>#include <boost/histogram/detail/make_default.hpp>#include <boost/histogram/detail/optional_index.hpp>#include <boost/histogram/detail/priority.hpp>#include <boost/histogram/detail/tuple_slice.hpp>#include <boost/histogram/fwd.hpp>#include <boost/mp11/algorithm.hpp>#include <boost/mp11/integral.hpp>#include <boost/mp11/tuple.hpp>#include <boost/mp11/utility.hpp>#include <mutex>#include <tuple>#include <type_traits>namespace boost {namespace histogram {namespace detail {template <class T, class U>struct sample_args_passed_vs_expected;template <class... Passed, class... Expected>struct sample_args_passed_vs_expected<std::tuple<Passed...>, std::tuple<Expected...>> {  static_assert(!(sizeof...(Expected) > 0 && sizeof...(Passed) == 0),                "error: accumulator requires samples, but sample argument is missing");  static_assert(      !(sizeof...(Passed) > 0 && sizeof...(Expected) == 0),      "error: accumulator does not accept samples, but sample argument is passed");  static_assert(sizeof...(Passed) == sizeof...(Expected),                "error: numbers of passed and expected sample arguments differ");  static_assert(      std::is_convertible<std::tuple<Passed...>, std::tuple<Expected...>>::value,      "error: sample argument(s) not convertible to accumulator argument(s)");};template <class A>struct storage_grower {  const A& axes_;  struct {    axis::index_type idx, old_extent;    std::size_t new_stride;  } data_[buffer_size<A>::value];  std::size_t new_size_;  storage_grower(const A& axes) noexcept : axes_(axes) {}  void from_shifts(const axis::index_type* shifts) noexcept {    auto dit = data_;    std::size_t s = 1;    for_each_axis(axes_, [&](const auto& a) {      const auto n = axis::traits::extent(a);      *dit++ = {0, n - std::abs(*shifts++), s};      s *= n;    });    new_size_ = s;  }  // must be extents before any shifts were applied  void from_extents(const axis::index_type* old_extents) noexcept {    auto dit = data_;    std::size_t s = 1;    for_each_axis(axes_, [&](const auto& a) {      const auto n = axis::traits::extent(a);      *dit++ = {0, *old_extents++, s};      s *= n;    });    new_size_ = s;  }  template <class S>  void apply(S& storage, const axis::index_type* shifts) {    auto new_storage = make_default(storage);    new_storage.reset(new_size_);    const auto dlast = data_ + axes_rank(axes_) - 1;    for (const auto& x : storage) {      auto ns = new_storage.begin();      auto sit = shifts;      auto dit = data_;      for_each_axis(axes_, [&](const auto& a) {        using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;        if (opt::test(axis::option::underflow)) {          if (dit->idx == 0) {            // axis has underflow and we are in the underflow bin:            // keep storage pointer unchanged            ++dit;            ++sit;            return;          }        }        if (opt::test(axis::option::overflow)) {          if (dit->idx == dit->old_extent - 1) {            // axis has overflow and we are in the overflow bin:            // move storage pointer to corresponding overflow bin position            ns += (axis::traits::extent(a) - 1) * dit->new_stride;            ++dit;            ++sit;            return;          }        }        // we are in a normal bin:        // move storage pointer to index position; apply positive shifts if any        ns += (dit->idx + (*sit >= 0 ? *sit : 0)) * dit->new_stride;        ++dit;        ++sit;      });      // assign old value to new location      *ns = x;      // advance multi-dimensional index      dit = data_;      ++dit->idx;      while (dit != dlast && dit->idx == dit->old_extent) {        dit->idx = 0;        ++(++dit)->idx;      }    }    storage = std::move(new_storage);  }};template <class T, class... Us>auto fill_storage_element_impl(priority<2>, T&& t, const Us&... args) noexcept    -> decltype(t(args...), void()) {  t(args...);}template <class T, class U>auto fill_storage_element_impl(priority<1>, T&& t, const weight_type<U>& w) noexcept    -> decltype(t += w, void()) {  t += w;}// fallback for arithmetic types and accumulators that do not handle the weighttemplate <class T, class U>auto fill_storage_element_impl(priority<0>, T&& t, const weight_type<U>& w) noexcept    -> decltype(t += w.value, void()) {  t += w.value;}template <class T>auto fill_storage_element_impl(priority<1>, T&& t) noexcept -> decltype(++t, void()) {  ++t;}template <class T, class... Us>void fill_storage_element(T&& t, const Us&... args) noexcept {  fill_storage_element_impl(priority<2>{}, std::forward<T>(t), args...);}// t may be a proxy and then it is an rvalue reference, not an lvalue referencetemplate <class IW, class IS, class T, class U>void fill_storage_2(IW, IS, T&& t, U&& u) noexcept {  mp11::tuple_apply(      [&](const auto&... args) {        fill_storage_element(std::forward<T>(t), std::get<IW::value>(u), args...);      },      std::get<IS::value>(u).value);}// t may be a proxy and then it is an rvalue reference, not an lvalue referencetemplate <class IS, class T, class U>void fill_storage_2(mp11::mp_int<-1>, IS, T&& t, const U& u) noexcept {  mp11::tuple_apply(      [&](const auto&... args) { fill_storage_element(std::forward<T>(t), args...); },      std::get<IS::value>(u).value);}// t may be a proxy and then it is an rvalue reference, not an lvalue referencetemplate <class IW, class T, class U>void fill_storage_2(IW, mp11::mp_int<-1>, T&& t, const U& u) noexcept {  fill_storage_element(std::forward<T>(t), std::get<IW::value>(u));}// t may be a proxy and then it is an rvalue reference, not an lvalue referencetemplate <class T, class U>void fill_storage_2(mp11::mp_int<-1>, mp11::mp_int<-1>, T&& t, const U&) noexcept {  fill_storage_element(std::forward<T>(t));}template <class IW, class IS, class Storage, class Index, class Args>auto fill_storage(IW, IS, Storage& s, const Index idx, const Args& a) noexcept {  if (is_valid(idx)) {    BOOST_ASSERT(idx < s.size());    fill_storage_2(IW{}, IS{}, s[idx], a);    return s.begin() + idx;  }  return s.end();}template <int S, int N>struct linearize_args {  template <class Index, class A, class Args>  static void impl(mp11::mp_int<N>, Index&, const std::size_t, A&, const Args&) {}  template <int I, class Index, class A, class Args>  static void impl(mp11::mp_int<I>, Index& o, const std::size_t s, A& ax,                   const Args& args) {    const auto e = linearize(o, s, axis_get<I>(ax), std::get<(S + I)>(args));    impl(mp11::mp_int<(I + 1)>{}, o, s * e, ax, args);  }  template <class Index, class A, class Args>  static void apply(Index& o, A& ax, const Args& args) {    impl(mp11::mp_int<0>{}, o, 1, ax, args);  }};template <int S>struct linearize_args<S, 1> {  template <class Index, class A, class Args>  static void apply(Index& o, A& ax, const Args& args) {    linearize(o, 1, axis_get<0>(ax), std::get<S>(args));  }};template <class A>constexpr unsigned min(const unsigned n) noexcept {  constexpr unsigned a = static_cast<unsigned>(buffer_size<A>::value);  return a < n ? a : n;}// not growingtemplate <class ArgTraits, class Storage, class Axes, class Args>auto fill_2(ArgTraits, mp11::mp_false, const std::size_t offset, Storage& st,            const Axes& axes, const Args& args) {  mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{offset};  linearize_args<ArgTraits::start::value, min<Axes>(ArgTraits::nargs::value)>::apply(      idx, axes, args);  return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx,                      args);}// at least one axis is growingtemplate <class ArgTraits, class Storage, class Axes, class Args>auto fill_2(ArgTraits, mp11::mp_true, const std::size_t, Storage& st, Axes& axes,            const Args& args) {  std::array<axis::index_type, ArgTraits::nargs::value> shifts;  // offset must be zero for linearize_growth  mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{0};  std::size_t stride = 1;  bool update_needed = false;  mp11::mp_for_each<mp11::mp_iota_c<min<Axes>(ArgTraits::nargs::value)>>([&](auto i) {    auto& ax = axis_get<i>(axes);    const auto extent = linearize_growth(idx, shifts[i], stride, ax,                                         std::get<(ArgTraits::start::value + i)>(args));    update_needed |= shifts[i] != 0;    stride *= extent;  });  if (update_needed) {    storage_grower<Axes> g(axes);    g.from_shifts(shifts.data());    g.apply(st, shifts.data());  }  return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx,                      args);}// pack original args tuple into another tuple (which is unpacked later)template <int Start, int Size, class IW, class IS, class Args>decltype(auto) pack_args(IW, IS, const Args& args) noexcept {  return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args),                         std::get<IS::value>(args));}template <int Start, int Size, class IW, class Args>decltype(auto) pack_args(IW, mp11::mp_int<-1>, const Args& args) noexcept {  return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args));}template <int Start, int Size, class IS, class Args>decltype(auto) pack_args(mp11::mp_int<-1>, IS, const Args& args) noexcept {  return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IS::value>(args));}template <int Start, int Size, class Args>decltype(auto) pack_args(mp11::mp_int<-1>, mp11::mp_int<-1>, const Args& args) noexcept {  return std::make_tuple(args);}#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)#pragma warning(disable : 4702) // fixing warning would reduce code readability a lot#endiftemplate <class ArgTraits, class S, class A, class Args>auto fill(std::true_type, ArgTraits, const std::size_t offset, S& storage, A& axes,          const Args& args) -> typename S::iterator {  using growing = has_growing_axis<A>;  // Sometimes we need to pack the tuple into another tuple:  // - histogram contains one axis which accepts tuple  // - user passes tuple to fill(...)  // Tuple is normally unpacked and arguments are processed, this causes pos::nargs > 1.  // Now we pack tuple into another tuple so that original tuple is send to axis.  // Notes:  // - has nice side-effect of making histogram::operator(1, 2) work as well  // - cannot detect call signature of axis at compile-time in all configurations  //   (axis::variant provides generic call interface and hides concrete  //   interface), so we throw at runtime if incompatible argument is passed (e.g.  //   3d tuple)  if (axes_rank(axes) == ArgTraits::nargs::value)    return fill_2(ArgTraits{}, growing{}, offset, storage, axes, args);  else if (axes_rank(axes) == 1 &&           axis::traits::rank(axis_get<0>(axes)) == ArgTraits::nargs::value)    return fill_2(        argument_traits_holder<            1, 0, (ArgTraits::wpos::value >= 0 ? 1 : -1),            (ArgTraits::spos::value >= 0 ? (ArgTraits::wpos::value >= 0 ? 2 : 1) : -1),            typename ArgTraits::sargs>{},        growing{}, offset, storage, axes,        pack_args<ArgTraits::start::value, ArgTraits::nargs::value>(            typename ArgTraits::wpos{}, typename ArgTraits::spos{}, args));  return (BOOST_THROW_EXCEPTION(              std::invalid_argument("number of arguments != histogram rank")),          storage.end());}#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)#pragma warning(default : 4702)#endif// empty implementation for bad arguments to stop compiler from showing internalstemplate <class ArgTraits, class S, class A, class Args>auto fill(std::false_type, ArgTraits, const std::size_t, S& storage, A&, const Args&) ->    typename S::iterator {  return storage.end();}} // namespace detail} // namespace histogram} // namespace boost#endif
 |