/* boost random/piecewise_linear_distribution.hpp header file * * Copyright Steven Watanabe 2011 * 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) * * See http://www.boost.org for most recent version including documentation. * * $Id$ */ #ifndef BOOST_RANDOM_PIECEWISE_LINEAR_DISTRIBUTION_HPP_INCLUDED #define BOOST_RANDOM_PIECEWISE_LINEAR_DISTRIBUTION_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include #ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST #include #endif #include #include namespace lslboost { namespace random { /** * The class @c piecewise_linear_distribution models a \random_distribution. */ template class piecewise_linear_distribution { public: typedef std::size_t input_type; typedef RealType result_type; class param_type { public: typedef piecewise_linear_distribution distribution_type; /** * Constructs a @c param_type object, representing a distribution * that produces values uniformly distributed in the range [0, 1). */ param_type() { _weights.push_back(RealType(1)); _weights.push_back(RealType(1)); _intervals.push_back(RealType(0)); _intervals.push_back(RealType(1)); } /** * Constructs a @c param_type object from two iterator ranges * containing the interval boundaries and weights at the boundaries. * If there are fewer than two boundaries, then this is equivalent to * the default constructor and the distribution will produce values * uniformly distributed in the range [0, 1). * * The values of the interval boundaries must be strictly * increasing, and the number of weights must be the same as * the number of interval boundaries. If there are extra * weights, they are ignored. */ template param_type(IntervalIter intervals_first, IntervalIter intervals_last, WeightIter weight_first) : _intervals(intervals_first, intervals_last) { if(_intervals.size() < 2) { _intervals.clear(); _weights.push_back(RealType(1)); _weights.push_back(RealType(1)); _intervals.push_back(RealType(0)); _intervals.push_back(RealType(1)); } else { _weights.reserve(_intervals.size()); for(std::size_t i = 0; i < _intervals.size(); ++i) { _weights.push_back(*weight_first++); } } } #ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST /** * Constructs a @c param_type object from an initializer_list * containing the interval boundaries and a unary function * specifying the weights at the boundaries. Each weight is * determined by calling the function at the corresponding point. * * If the initializer_list contains fewer than two elements, * this is equivalent to the default constructor and the * distribution will produce values uniformly distributed * in the range [0, 1). */ template param_type(const std::initializer_list& il, F f) : _intervals(il.begin(), il.end()) { if(_intervals.size() < 2) { _intervals.clear(); _weights.push_back(RealType(1)); _weights.push_back(RealType(1)); _intervals.push_back(RealType(0)); _intervals.push_back(RealType(1)); } else { _weights.reserve(_intervals.size()); for(typename std::vector::const_iterator iter = _intervals.begin(), end = _intervals.end(); iter != end; ++iter) { _weights.push_back(f(*iter)); } } } #endif /** * Constructs a @c param_type object from Boost.Range ranges holding * the interval boundaries and the weights at the boundaries. If * there are fewer than two interval boundaries, this is equivalent * to the default constructor and the distribution will produce * values uniformly distributed in the range [0, 1). The * number of weights must be equal to the number of * interval boundaries. */ template param_type(const IntervalRange& intervals_arg, const WeightRange& weights_arg) : _intervals(lslboost::begin(intervals_arg), lslboost::end(intervals_arg)), _weights(lslboost::begin(weights_arg), lslboost::end(weights_arg)) { if(_intervals.size() < 2) { _weights.clear(); _weights.push_back(RealType(1)); _weights.push_back(RealType(1)); _intervals.clear(); _intervals.push_back(RealType(0)); _intervals.push_back(RealType(1)); } } /** * Constructs the parameters for a distribution that approximates a * function. The range of the distribution is [xmin, xmax). This * range is divided into nw equally sized intervals and the weights * are found by calling the unary function f on the boundaries of the * intervals. */ template param_type(std::size_t nw, RealType xmin, RealType xmax, F f) { std::size_t n = (nw == 0) ? 1 : nw; double delta = (xmax - xmin) / n; BOOST_ASSERT(delta > 0); for(std::size_t k = 0; k < n; ++k) { _weights.push_back(f(xmin + k*delta)); _intervals.push_back(xmin + k*delta); } _weights.push_back(f(xmax)); _intervals.push_back(xmax); } /** Returns a vector containing the interval boundaries. */ std::vector intervals() const { return _intervals; } /** * Returns a vector containing the probability densities * at all the interval boundaries. */ std::vector densities() const { RealType sum = static_cast(0); for(std::size_t i = 0; i < _intervals.size() - 1; ++i) { RealType width = _intervals[i + 1] - _intervals[i]; sum += (_weights[i] + _weights[i + 1]) * width / 2; } std::vector result; result.reserve(_weights.size()); for(typename std::vector::const_iterator iter = _weights.begin(), end = _weights.end(); iter != end; ++iter) { result.push_back(*iter / sum); } return result; } /** Writes the parameters to a @c std::ostream. */ BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, param_type, parm) { detail::print_vector(os, parm._intervals); detail::print_vector(os, parm._weights); return os; } /** Reads the parameters from a @c std::istream. */ BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, param_type, parm) { std::vector new_intervals; std::vector new_weights; detail::read_vector(is, new_intervals); detail::read_vector(is, new_weights); if(is) { parm._intervals.swap(new_intervals); parm._weights.swap(new_weights); } return is; } /** Returns true if the two sets of parameters are the same. */ BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(param_type, lhs, rhs) { return lhs._intervals == rhs._intervals && lhs._weights == rhs._weights; } /** Returns true if the two sets of parameters are different. */ BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(param_type) private: friend class piecewise_linear_distribution; std::vector _intervals; std::vector _weights; }; /** * Creates a new @c piecewise_linear_distribution that * produces values uniformly distributed in the range [0, 1). */ piecewise_linear_distribution() { default_init(); } /** * Constructs a piecewise_linear_distribution from two iterator ranges * containing the interval boundaries and the weights at the boundaries. * If there are fewer than two boundaries, then this is equivalent to * the default constructor and creates a distribution that * produces values uniformly distributed in the range [0, 1). * * The values of the interval boundaries must be strictly * increasing, and the number of weights must be equal to * the number of interval boundaries. If there are extra * weights, they are ignored. * * For example, * * @code * double intervals[] = { 0.0, 1.0, 2.0 }; * double weights[] = { 0.0, 1.0, 0.0 }; * piecewise_constant_distribution<> dist( * &intervals[0], &intervals[0] + 3, &weights[0]); * @endcode * * produces a triangle distribution. */ template piecewise_linear_distribution(IntervalIter first_interval, IntervalIter last_interval, WeightIter first_weight) : _intervals(first_interval, last_interval) { if(_intervals.size() < 2) { default_init(); } else { _weights.reserve(_intervals.size()); for(std::size_t i = 0; i < _intervals.size(); ++i) { _weights.push_back(*first_weight++); } init(); } } #ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST /** * Constructs a piecewise_linear_distribution from an * initializer_list containing the interval boundaries * and a unary function specifying the weights. Each * weight is determined by calling the function at the * corresponding interval boundary. * * If the initializer_list contains fewer than two elements, * this is equivalent to the default constructor and the * distribution will produce values uniformly distributed * in the range [0, 1). */ template piecewise_linear_distribution(std::initializer_list il, F f) : _intervals(il.begin(), il.end()) { if(_intervals.size() < 2) { default_init(); } else { _weights.reserve(_intervals.size()); for(typename std::vector::const_iterator iter = _intervals.begin(), end = _intervals.end(); iter != end; ++iter) { _weights.push_back(f(*iter)); } init(); } } #endif /** * Constructs a piecewise_linear_distribution from Boost.Range * ranges holding the interval boundaries and the weights. If * there are fewer than two interval boundaries, this is equivalent * to the default constructor and the distribution will produce * values uniformly distributed in the range [0, 1). The * number of weights must be equal to the number of * interval boundaries. */ template piecewise_linear_distribution(const IntervalsRange& intervals_arg, const WeightsRange& weights_arg) : _intervals(lslboost::begin(intervals_arg), lslboost::end(intervals_arg)), _weights(lslboost::begin(weights_arg), lslboost::end(weights_arg)) { if(_intervals.size() < 2) { default_init(); } else { init(); } } /** * Constructs a piecewise_linear_distribution that approximates a * function. The range of the distribution is [xmin, xmax). This * range is divided into nw equally sized intervals and the weights * are found by calling the unary function f on the interval boundaries. */ template piecewise_linear_distribution(std::size_t nw, RealType xmin, RealType xmax, F f) { if(nw == 0) { nw = 1; } RealType delta = (xmax - xmin) / nw; _intervals.reserve(nw + 1); for(std::size_t i = 0; i < nw; ++i) { RealType x = xmin + i * delta; _intervals.push_back(x); _weights.push_back(f(x)); } _intervals.push_back(xmax); _weights.push_back(f(xmax)); init(); } /** * Constructs a piecewise_linear_distribution from its parameters. */ explicit piecewise_linear_distribution(const param_type& parm) : _intervals(parm._intervals), _weights(parm._weights) { init(); } /** * Returns a value distributed according to the parameters of the * piecewise_linear_distribution. */ template RealType operator()(URNG& urng) const { std::size_t i = _bins(urng); bool is_in_rectangle = (i % 2 == 0); i = i / 2; uniform_real dist(_intervals[i], _intervals[i+1]); if(is_in_rectangle) { return dist(urng); } else if(_weights[i] < _weights[i+1]) { return (std::max)(dist(urng), dist(urng)); } else { return (std::min)(dist(urng), dist(urng)); } } /** * Returns a value distributed according to the parameters * specified by param. */ template RealType operator()(URNG& urng, const param_type& parm) const { return piecewise_linear_distribution(parm)(urng); } /** Returns the smallest value that the distribution can produce. */ result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () const { return _intervals.front(); } /** Returns the largest value that the distribution can produce. */ result_type max BOOST_PREVENT_MACRO_SUBSTITUTION () const { return _intervals.back(); } /** * Returns a vector containing the probability densities * at the interval boundaries. */ std::vector densities() const { RealType sum = static_cast(0); for(std::size_t i = 0; i < _intervals.size() - 1; ++i) { RealType width = _intervals[i + 1] - _intervals[i]; sum += (_weights[i] + _weights[i + 1]) * width / 2; } std::vector result; result.reserve(_weights.size()); for(typename std::vector::const_iterator iter = _weights.begin(), end = _weights.end(); iter != end; ++iter) { result.push_back(*iter / sum); } return result; } /** Returns a vector containing the interval boundaries. */ std::vector intervals() const { return _intervals; } /** Returns the parameters of the distribution. */ param_type param() const { return param_type(_intervals, _weights); } /** Sets the parameters of the distribution. */ void param(const param_type& parm) { std::vector new_intervals(parm._intervals); std::vector new_weights(parm._weights); init(new_intervals, new_weights); _intervals.swap(new_intervals); _weights.swap(new_weights); } /** * Effects: Subsequent uses of the distribution do not depend * on values produced by any engine prior to invoking reset. */ void reset() { _bins.reset(); } /** Writes a distribution to a @c std::ostream. */ BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR( os, piecewise_linear_distribution, pld) { os << pld.param(); return os; } /** Reads a distribution from a @c std::istream */ BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR( is, piecewise_linear_distribution, pld) { param_type parm; if(is >> parm) { pld.param(parm); } return is; } /** * Returns true if the two distributions will return the * same sequence of values, when passed equal generators. */ BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR( piecewise_linear_distribution, lhs, rhs) { return lhs._intervals == rhs._intervals && lhs._weights == rhs._weights; } /** * Returns true if the two distributions may return different * sequences of values, when passed equal generators. */ BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(piecewise_linear_distribution) private: /// @cond \show_private void init(const std::vector& intervals_arg, const std::vector& weights_arg) { using std::abs; std::vector bin_weights; bin_weights.reserve((intervals_arg.size() - 1) * 2); for(std::size_t i = 0; i < intervals_arg.size() - 1; ++i) { RealType width = intervals_arg[i + 1] - intervals_arg[i]; RealType w1 = weights_arg[i]; RealType w2 = weights_arg[i + 1]; bin_weights.push_back((std::min)(w1, w2) * width); bin_weights.push_back(abs(w1 - w2) * width / 2); } typedef discrete_distribution bins_type; typename bins_type::param_type bins_param(bin_weights); _bins.param(bins_param); } void init() { init(_intervals, _weights); } void default_init() { _intervals.clear(); _intervals.push_back(RealType(0)); _intervals.push_back(RealType(1)); _weights.clear(); _weights.push_back(RealType(1)); _weights.push_back(RealType(1)); init(); } discrete_distribution _bins; std::vector _intervals; std::vector _weights; /// @endcond }; } } #endif