// 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_LINEARIZE_HPP #define BOOST_HISTOGRAM_DETAIL_LINEARIZE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace histogram { namespace detail { template struct is_accumulator_set : std::false_type {}; template using has_underflow = decltype(axis::traits::static_options::test(axis::option::underflow)); template struct is_growing : decltype(axis::traits::static_options::test(axis::option::growth)) {}; template struct is_growing> : mp11::mp_or...> {}; template struct is_growing> : mp11::mp_or...> {}; template using has_growing_axis = mp11::mp_if, is_growing>, is_growing>; /// Index with an invalid state struct optional_index { std::size_t idx = 0; std::size_t stride = 1; operator bool() const { return stride > 0; } std::size_t operator*() const { return idx; } }; inline void linearize(optional_index& out, const axis::index_type extent, const axis::index_type j) noexcept { // j is internal index shifted by +1 if axis has underflow bin out.idx += j * out.stride; // set stride to 0, if j is invalid out.stride *= (0 <= j && j < extent) * extent; } // for non-growing axis template void linearize_value(optional_index& o, const Axis& a, const Value& v) { using B = decltype(axis::traits::static_options::test(axis::option::underflow)); const auto j = axis::traits::index(a, v) + B::value; linearize(o, axis::traits::extent(a), j); } // for variant that does not contain any growing axis template void linearize_value(optional_index& o, const axis::variant& a, const Value& v) { axis::visit([&o, &v](const auto& a) { linearize_value(o, a, v); }, a); } // for growing axis template void linearize_value(optional_index& o, axis::index_type& s, Axis& a, const Value& v) { axis::index_type j; std::tie(j, s) = axis::traits::update(a, v); j += has_underflow::value; linearize(o, axis::traits::extent(a), j); } // for variant which contains at least one growing axis template void linearize_value(optional_index& o, axis::index_type& s, axis::variant& a, const Value& v) { axis::visit([&o, &s, &v](auto&& a) { linearize_value(o, s, a, v); }, a); } template void linearize_index(optional_index& out, const A& axis, const axis::index_type j) { // A may be axis or variant, cannot use static option detection here const auto opt = axis::traits::options(axis); const auto shift = opt & axis::option::underflow ? 1 : 0; const auto n = axis.size() + (opt & axis::option::overflow ? 1 : 0); linearize(out, n + shift, j + shift); } template void maybe_replace_storage(S& storage, const A& axes, const T& shifts) { bool update_needed = false; auto sit = shifts; for_each_axis(axes, [&](const auto&) { update_needed |= (*sit++ != 0); }); if (!update_needed) return; struct item { axis::index_type idx, old_extent; std::size_t new_stride; } data[buffer_size::value]; sit = shifts; 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(*sit++), s}; s *= n; }); auto new_storage = make_default(storage); new_storage.reset(detail::bincount(axes)); const auto dlast = data + get_size(axes) - 1; for (const auto& x : storage) { auto ns = new_storage.begin(); sit = shifts; dit = data; for_each_axis(axes, [&](const auto& a) { using opt = axis::traits::static_options; 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 ns += (dit->idx + std::max(*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); } // special case: if histogram::operator()(tuple(1, 2)) is called on 1d histogram // with axis that accepts 2d tuple, this should not fail // - solution is to forward tuples of size > 1 directly to axis for 1d // histograms // - 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) // histogram has only non-growing axes template optional_index args_to_index(std::false_type, S&, const T& axes, const U& args) { optional_index idx; const auto rank = get_size(axes); if (rank == 1 && N > 1) linearize_value(idx, axis_get<0>(axes), tuple_slice(args)); else { if (rank != N) BOOST_THROW_EXCEPTION( std::invalid_argument("number of arguments != histogram rank")); constexpr unsigned M = buffer_size>::value; mp11::mp_for_each>([&](auto J) { linearize_value(idx, axis_get(axes), std::get<(J + I)>(args)); }); } return idx; } // histogram has growing axes template optional_index args_to_index(std::true_type, S& storage, T& axes, const U& args) { optional_index idx; axis::index_type shifts[buffer_size::value]; const auto rank = get_size(axes); if (rank == 1 && N > 1) linearize_value(idx, shifts[0], axis_get<0>(axes), tuple_slice(args)); else { if (rank != N) BOOST_THROW_EXCEPTION( std::invalid_argument("number of arguments != histogram rank")); constexpr unsigned M = buffer_size>::value; mp11::mp_for_each>([&](auto J) { linearize_value(idx, shifts[J], axis_get(axes), std::get<(J + I)>(args)); }); } maybe_replace_storage(storage, axes, shifts); return idx; } template constexpr auto weight_sample_indices() { if (is_weight::value) return std::make_pair(0, -1); if (is_sample::value) return std::make_pair(-1, 0); return std::make_pair(-1, -1); } template constexpr auto weight_sample_indices() { using L = mp11::mp_list; const int n = sizeof...(Us) + 1; if (is_weight>::value) { if (is_sample>::value) return std::make_pair(0, 1); if (is_sample>::value) return std::make_pair(0, n); return std::make_pair(0, -1); } if (is_sample>::value) { if (is_weight>::value) return std::make_pair(1, 0); if (is_weight>::value) return std::make_pair(n, 0); return std::make_pair(-1, 0); } if (is_weight>::value) { // 0, n already covered if (is_sample>::value) return std::make_pair(n, n - 1); return std::make_pair(n, -1); } if (is_sample>::value) { // n, 0 already covered if (is_weight>::value) return std::make_pair(n - 1, n); return std::make_pair(-1, n); } return std::make_pair(-1, -1); } template void fill_storage(mp11::mp_int<-1>, mp11::mp_int<-1>, T&& t, U&&) { static_if>>( [](auto&& t) { ++t; }, [](auto&& t) { t(); }, std::forward(t)); } template void fill_storage(IW, mp11::mp_int<-1>, T&& t, U&& args) { static_if>>( [](auto&& t, const auto& w) { t += w; }, [](auto&& t, const auto& w) { #ifdef BOOST_HISTOGRAM_WITH_ACCUMULATORS_SUPPORT static_if>>( [w](auto&& t) { t(::boost::accumulators::weight = w); }, [w](auto&& t) { t(w); }, t); #else t(w); #endif }, std::forward(t), std::get(args).value); } template void fill_storage(mp11::mp_int<-1>, IS, T&& t, U&& args) { mp11::tuple_apply([&t](auto&&... args) { t(args...); }, std::get(args).value); } template void fill_storage(IW, IS, T&& t, U&& args) { mp11::tuple_apply( [&](auto&&... args2) { t(std::get(args).value, args2...); }, std::get(args).value); } template auto fill(S& storage, A& axes, const std::tuple& args) { constexpr auto iws = weight_sample_indices(); constexpr unsigned n = sizeof...(Us) - (iws.first > -1) - (iws.second > -1); constexpr unsigned i = (iws.first == 0 || iws.second == 0) ? (iws.first == 1 || iws.second == 1 ? 2 : 1) : 0; optional_index idx = args_to_index(has_growing_axis(), storage, axes, args); if (idx) { fill_storage(mp11::mp_int(), mp11::mp_int(), storage[*idx], args); return storage.begin() + *idx; } return storage.end(); } template optional_index at(const A& axes, const std::tuple& args) { if (get_size(axes) != sizeof...(Us)) BOOST_THROW_EXCEPTION(std::invalid_argument("number of arguments != histogram rank")); optional_index idx; mp11::mp_for_each>([&](auto I) { // axes_get works with static and dynamic axes linearize_index(idx, axis_get(axes), static_cast(std::get(args))); }); return idx; } template optional_index at(const A& axes, const U& args) { if (get_size(axes) != get_size(args)) BOOST_THROW_EXCEPTION(std::invalid_argument("number of arguments != histogram rank")); optional_index idx; using std::begin; auto it = begin(args); for_each_axis(axes, [&](const auto& a) { linearize_index(idx, a, static_cast(*it++)); }); return idx; } } // namespace detail } // namespace histogram } // namespace boost #endif