// 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_AXIS_REGULAR_HPP #define BOOST_HISTOGRAM_AXIS_REGULAR_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace histogram { namespace detail { template using get_scale_type_helper = typename T::value_type; template using get_scale_type = mp11::mp_eval_or; struct one_unit {}; template T operator*(T&& t, const one_unit&) { return std::forward(t); } template T operator/(T&& t, const one_unit&) { return std::forward(t); } template using get_unit_type_helper = typename T::unit_type; template using get_unit_type = mp11::mp_eval_or; template > R get_scale(const T& t) { return t / get_unit_type(); } } // namespace detail namespace axis { namespace transform { /// Identity transform for equidistant bins. struct id { /// Pass-through. template static T forward(T&& x) noexcept { return std::forward(x); } /// Pass-through. template static T inverse(T&& x) noexcept { return std::forward(x); } template void serialize(Archive&, unsigned /* version */) {} }; /// Log transform for equidistant bins in log-space. struct log { /// Returns log(x) of external value x. template static T forward(T x) { return std::log(x); } /// Returns exp(x) for internal value x. template static T inverse(T x) { return std::exp(x); } template void serialize(Archive&, unsigned /* version */) {} }; /// Sqrt transform for equidistant bins in sqrt-space. struct sqrt { /// Returns sqrt(x) of external value x. template static T forward(T x) { return std::sqrt(x); } /// Returns x^2 of internal value x. template static T inverse(T x) { return x * x; } template void serialize(Archive&, unsigned /* version */) {} }; /// Pow transform for equidistant bins in pow-space. struct pow { double power = 1; /**< power index */ /// Make transform with index p. explicit pow(double p) : power(p) {} pow() = default; /// Returns pow(x, power) of external value x. template auto forward(T x) const { return std::pow(x, power); } /// Returns pow(x, 1/power) of external value x. template auto inverse(T x) const { return std::pow(x, 1.0 / power); } bool operator==(const pow& o) const noexcept { return power == o.power; } template void serialize(Archive& ar, unsigned /* version */) { ar& make_nvp("power", power); } }; } // namespace transform #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED // Type envelope to mark value as step size template struct step_type { T value; }; #endif /** Helper function to mark argument as step size. */ template step_type step(T t) { return step_type{t}; } /** Axis for equidistant intervals on the real line. The most common binning strategy. Very fast. Binning is a O(1) operation. @tparam Value input value type, must be floating point. @tparam Transform builtin or user-defined transform type. @tparam MetaData type to store meta data. @tparam Options see boost::histogram::axis::option (all values allowed). */ template class regular : public iterator_mixin>, protected detail::replace_default, public metadata_base { using value_type = Value; using transform_type = detail::replace_default; using metadata_type = typename metadata_base::metadata_type; using options_type = detail::replace_default; static_assert(std::is_nothrow_move_constructible::value, "transform must be no-throw move constructible"); static_assert(std::is_nothrow_move_assignable::value, "transform must be no-throw move assignable"); using unit_type = detail::get_unit_type; using internal_value_type = detail::get_scale_type; static_assert(std::is_floating_point::value, "regular axis requires floating point type"); static_assert( (!options_type::test(option::circular) && !options_type::test(option::growth)) || (options_type::test(option::circular) ^ options_type::test(option::growth)), "circular and growth options are mutually exclusive"); public: constexpr regular() = default; /** Construct n bins over real transformed range [start, stop). * * @param trans transform instance to use. * @param n number of bins. * @param start low edge of first bin. * @param stop high edge of last bin. * @param meta description of the axis (optional). */ regular(transform_type trans, unsigned n, value_type start, value_type stop, metadata_type meta = {}) : transform_type(std::move(trans)) , metadata_base(std::move(meta)) , size_(static_cast(n)) , min_(this->forward(detail::get_scale(start))) , delta_(this->forward(detail::get_scale(stop)) - min_) { if (size() == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required")); if (!std::isfinite(min_) || !std::isfinite(delta_)) BOOST_THROW_EXCEPTION( std::invalid_argument("forward transform of start or stop invalid")); if (delta_ == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("range of axis is zero")); } /** Construct n bins over real range [start, stop). * * @param n number of bins. * @param start low edge of first bin. * @param stop high edge of last bin. * @param meta description of the axis (optional). */ regular(unsigned n, value_type start, value_type stop, metadata_type meta = {}) : regular({}, n, start, stop, std::move(meta)) {} /** Construct bins with the given step size over real transformed range * [start, stop). * * @param trans transform instance to use. * @param step width of a single bin. * @param start low edge of first bin. * @param stop upper limit of high edge of last bin (see below). * @param meta description of the axis (optional). * * The axis computes the number of bins as n = abs(stop - start) / step, * rounded down. This means that stop is an upper limit to the actual value * (start + n * step). */ template regular(transform_type trans, step_type step, value_type start, value_type stop, metadata_type meta = {}) : regular(trans, static_cast(std::abs(stop - start) / step.value), start, start + static_cast(std::abs(stop - start) / step.value) * step.value, std::move(meta)) {} /** Construct bins with the given step size over real range [start, stop). * * @param step width of a single bin. * @param start low edge of first bin. * @param stop upper limit of high edge of last bin (see below). * @param meta description of the axis (optional). * * The axis computes the number of bins as n = abs(stop - start) / step, * rounded down. This means that stop is an upper limit to the actual value * (start + n * step). */ template regular(step_type step, value_type start, value_type stop, metadata_type meta = {}) : regular({}, step, start, stop, std::move(meta)) {} /// Constructor used by algorithm::reduce to shrink and rebin (not for users). regular(const regular& src, index_type begin, index_type end, unsigned merge) : regular(src.transform(), (end - begin) / merge, src.value(begin), src.value(end), src.metadata()) { BOOST_ASSERT((end - begin) % merge == 0); if (options_type::test(option::circular) && !(begin == 0 && end == src.size())) BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis")); } /// Return instance of the transform type. const transform_type& transform() const noexcept { return *this; } /// Return index for value argument. index_type index(value_type x) const noexcept { // Runs in hot loop, please measure impact of changes auto z = (this->forward(x / unit_type{}) - min_) / delta_; if (options_type::test(option::circular)) { if (std::isfinite(z)) { z -= std::floor(z); return static_cast(z * size()); } } else { if (z < 1) { if (z >= 0) return static_cast(z * size()); else return -1; } } return size(); // also returned if x is NaN } /// Returns index and shift (if axis has grown) for the passed argument. auto update(value_type x) noexcept { BOOST_ASSERT(options_type::test(option::growth)); const auto z = (this->forward(x / unit_type{}) - min_) / delta_; if (z < 1) { // don't use i here! if (z >= 0) { const auto i = static_cast(z * size()); return std::make_pair(i, 0); } if (z != -std::numeric_limits::infinity()) { const auto stop = min_ + delta_; const auto i = static_cast(std::floor(z * size())); min_ += i * (delta_ / size()); delta_ = stop - min_; size_ -= i; return std::make_pair(0, -i); } // z is -infinity return std::make_pair(-1, 0); } // z either beyond range, infinite, or NaN if (z < std::numeric_limits::infinity()) { const auto i = static_cast(z * size()); const auto n = i - size() + 1; delta_ /= size(); delta_ *= size() + n; size_ += n; return std::make_pair(i, -n); } // z either infinite or NaN return std::make_pair(size(), 0); } /// Return value for fractional index argument. value_type value(real_index_type i) const noexcept { auto z = i / size(); if (!options_type::test(option::circular) && z < 0.0) z = -std::numeric_limits::infinity() * delta_; else if (options_type::test(option::circular) || z <= 1.0) z = (1.0 - z) * min_ + z * (min_ + delta_); else { z = std::numeric_limits::infinity() * delta_; } return static_cast(this->inverse(z) * unit_type()); } /// Return bin for index argument. decltype(auto) bin(index_type idx) const noexcept { return interval_view(*this, idx); } /// Returns the number of bins, without over- or underflow. index_type size() const noexcept { return size_; } /// Returns the options. static constexpr unsigned options() noexcept { return options_type::value; } template bool operator==(const regular& o) const noexcept { return detail::relaxed_equal(transform(), o.transform()) && size() == o.size() && min_ == o.min_ && delta_ == o.delta_ && metadata_base::operator==(o); } template bool operator!=(const regular& o) const noexcept { return !operator==(o); } template void serialize(Archive& ar, unsigned /* version */) { ar& make_nvp("transform", static_cast(*this)); ar& make_nvp("size", size_); ar& make_nvp("meta", this->metadata()); ar& make_nvp("min", min_); ar& make_nvp("delta", delta_); } private: index_type size_{0}; internal_value_type min_{0}, delta_{1}; template friend class regular; }; #if __cpp_deduction_guides >= 201606 template regular(unsigned, T, T) ->regular, transform::id, null_type>; template regular(unsigned, T, T, M) ->regular, transform::id, detail::replace_cstring>>; template > regular(Tr, unsigned, T, T)->regular, Tr, null_type>; template regular(Tr, unsigned, T, T, M) ->regular, Tr, detail::replace_cstring>>; #endif /// Regular axis with circular option already set. template #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED using circular = regular{} | option::circular)>; #else class circular; #endif } // namespace axis } // namespace histogram } // namespace boost #endif