piecewise_linear_distribution.hpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. /* boost random/piecewise_linear_distribution.hpp header file
  2. *
  3. * Copyright Steven Watanabe 2011
  4. * Distributed under the Boost Software License, Version 1.0. (See
  5. * accompanying file LICENSE_1_0.txt or copy at
  6. * http://www.boost.org/LICENSE_1_0.txt)
  7. *
  8. * See http://www.boost.org for most recent version including documentation.
  9. *
  10. * $Id$
  11. */
  12. #ifndef BOOST_RANDOM_PIECEWISE_LINEAR_DISTRIBUTION_HPP_INCLUDED
  13. #define BOOST_RANDOM_PIECEWISE_LINEAR_DISTRIBUTION_HPP_INCLUDED
  14. #include <vector>
  15. #include <algorithm>
  16. #include <cmath>
  17. #include <cstdlib>
  18. #include <boost/assert.hpp>
  19. #include <boost/random/uniform_real.hpp>
  20. #include <boost/random/discrete_distribution.hpp>
  21. #include <boost/random/detail/config.hpp>
  22. #include <boost/random/detail/operators.hpp>
  23. #include <boost/random/detail/vector_io.hpp>
  24. #ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST
  25. #include <initializer_list>
  26. #endif
  27. #include <boost/range/begin.hpp>
  28. #include <boost/range/end.hpp>
  29. namespace boost {
  30. namespace random {
  31. /**
  32. * The class @c piecewise_linear_distribution models a \random_distribution.
  33. */
  34. template<class RealType = double>
  35. class piecewise_linear_distribution {
  36. public:
  37. typedef std::size_t input_type;
  38. typedef RealType result_type;
  39. class param_type {
  40. public:
  41. typedef piecewise_linear_distribution distribution_type;
  42. /**
  43. * Constructs a @c param_type object, representing a distribution
  44. * that produces values uniformly distributed in the range [0, 1).
  45. */
  46. param_type()
  47. {
  48. _weights.push_back(RealType(1));
  49. _weights.push_back(RealType(1));
  50. _intervals.push_back(RealType(0));
  51. _intervals.push_back(RealType(1));
  52. }
  53. /**
  54. * Constructs a @c param_type object from two iterator ranges
  55. * containing the interval boundaries and weights at the boundaries.
  56. * If there are fewer than two boundaries, then this is equivalent to
  57. * the default constructor and the distribution will produce values
  58. * uniformly distributed in the range [0, 1).
  59. *
  60. * The values of the interval boundaries must be strictly
  61. * increasing, and the number of weights must be the same as
  62. * the number of interval boundaries. If there are extra
  63. * weights, they are ignored.
  64. */
  65. template<class IntervalIter, class WeightIter>
  66. param_type(IntervalIter intervals_first, IntervalIter intervals_last,
  67. WeightIter weight_first)
  68. : _intervals(intervals_first, intervals_last)
  69. {
  70. if(_intervals.size() < 2) {
  71. _intervals.clear();
  72. _weights.push_back(RealType(1));
  73. _weights.push_back(RealType(1));
  74. _intervals.push_back(RealType(0));
  75. _intervals.push_back(RealType(1));
  76. } else {
  77. _weights.reserve(_intervals.size());
  78. for(std::size_t i = 0; i < _intervals.size(); ++i) {
  79. _weights.push_back(*weight_first++);
  80. }
  81. }
  82. }
  83. #ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST
  84. /**
  85. * Constructs a @c param_type object from an initializer_list
  86. * containing the interval boundaries and a unary function
  87. * specifying the weights at the boundaries. Each weight is
  88. * determined by calling the function at the corresponding point.
  89. *
  90. * If the initializer_list contains fewer than two elements,
  91. * this is equivalent to the default constructor and the
  92. * distribution will produce values uniformly distributed
  93. * in the range [0, 1).
  94. */
  95. template<class T, class F>
  96. param_type(const std::initializer_list<T>& il, F f)
  97. : _intervals(il.begin(), il.end())
  98. {
  99. if(_intervals.size() < 2) {
  100. _intervals.clear();
  101. _weights.push_back(RealType(1));
  102. _weights.push_back(RealType(1));
  103. _intervals.push_back(RealType(0));
  104. _intervals.push_back(RealType(1));
  105. } else {
  106. _weights.reserve(_intervals.size());
  107. for(typename std::vector<RealType>::const_iterator
  108. iter = _intervals.begin(), end = _intervals.end();
  109. iter != end; ++iter)
  110. {
  111. _weights.push_back(f(*iter));
  112. }
  113. }
  114. }
  115. #endif
  116. /**
  117. * Constructs a @c param_type object from Boost.Range ranges holding
  118. * the interval boundaries and the weights at the boundaries. If
  119. * there are fewer than two interval boundaries, this is equivalent
  120. * to the default constructor and the distribution will produce
  121. * values uniformly distributed in the range [0, 1). The
  122. * number of weights must be equal to the number of
  123. * interval boundaries.
  124. */
  125. template<class IntervalRange, class WeightRange>
  126. param_type(const IntervalRange& intervals_arg,
  127. const WeightRange& weights_arg)
  128. : _intervals(boost::begin(intervals_arg), boost::end(intervals_arg)),
  129. _weights(boost::begin(weights_arg), boost::end(weights_arg))
  130. {
  131. if(_intervals.size() < 2) {
  132. _weights.clear();
  133. _weights.push_back(RealType(1));
  134. _weights.push_back(RealType(1));
  135. _intervals.clear();
  136. _intervals.push_back(RealType(0));
  137. _intervals.push_back(RealType(1));
  138. }
  139. }
  140. /**
  141. * Constructs the parameters for a distribution that approximates a
  142. * function. The range of the distribution is [xmin, xmax). This
  143. * range is divided into nw equally sized intervals and the weights
  144. * are found by calling the unary function f on the boundaries of the
  145. * intervals.
  146. */
  147. template<class F>
  148. param_type(std::size_t nw, RealType xmin, RealType xmax, F f)
  149. {
  150. std::size_t n = (nw == 0) ? 1 : nw;
  151. double delta = (xmax - xmin) / n;
  152. BOOST_ASSERT(delta > 0);
  153. for(std::size_t k = 0; k < n; ++k) {
  154. _weights.push_back(f(xmin + k*delta));
  155. _intervals.push_back(xmin + k*delta);
  156. }
  157. _weights.push_back(f(xmax));
  158. _intervals.push_back(xmax);
  159. }
  160. /** Returns a vector containing the interval boundaries. */
  161. std::vector<RealType> intervals() const { return _intervals; }
  162. /**
  163. * Returns a vector containing the probability densities
  164. * at all the interval boundaries.
  165. */
  166. std::vector<RealType> densities() const
  167. {
  168. RealType sum = static_cast<RealType>(0);
  169. for(std::size_t i = 0; i < _intervals.size() - 1; ++i) {
  170. RealType width = _intervals[i + 1] - _intervals[i];
  171. sum += (_weights[i] + _weights[i + 1]) * width / 2;
  172. }
  173. std::vector<RealType> result;
  174. result.reserve(_weights.size());
  175. for(typename std::vector<RealType>::const_iterator
  176. iter = _weights.begin(), end = _weights.end();
  177. iter != end; ++iter)
  178. {
  179. result.push_back(*iter / sum);
  180. }
  181. return result;
  182. }
  183. /** Writes the parameters to a @c std::ostream. */
  184. BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, param_type, parm)
  185. {
  186. detail::print_vector(os, parm._intervals);
  187. detail::print_vector(os, parm._weights);
  188. return os;
  189. }
  190. /** Reads the parameters from a @c std::istream. */
  191. BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, param_type, parm)
  192. {
  193. std::vector<RealType> new_intervals;
  194. std::vector<RealType> new_weights;
  195. detail::read_vector(is, new_intervals);
  196. detail::read_vector(is, new_weights);
  197. if(is) {
  198. parm._intervals.swap(new_intervals);
  199. parm._weights.swap(new_weights);
  200. }
  201. return is;
  202. }
  203. /** Returns true if the two sets of parameters are the same. */
  204. BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(param_type, lhs, rhs)
  205. {
  206. return lhs._intervals == rhs._intervals
  207. && lhs._weights == rhs._weights;
  208. }
  209. /** Returns true if the two sets of parameters are different. */
  210. BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(param_type)
  211. private:
  212. friend class piecewise_linear_distribution;
  213. std::vector<RealType> _intervals;
  214. std::vector<RealType> _weights;
  215. };
  216. /**
  217. * Creates a new @c piecewise_linear_distribution that
  218. * produces values uniformly distributed in the range [0, 1).
  219. */
  220. piecewise_linear_distribution()
  221. {
  222. default_init();
  223. }
  224. /**
  225. * Constructs a piecewise_linear_distribution from two iterator ranges
  226. * containing the interval boundaries and the weights at the boundaries.
  227. * If there are fewer than two boundaries, then this is equivalent to
  228. * the default constructor and creates a distribution that
  229. * produces values uniformly distributed in the range [0, 1).
  230. *
  231. * The values of the interval boundaries must be strictly
  232. * increasing, and the number of weights must be equal to
  233. * the number of interval boundaries. If there are extra
  234. * weights, they are ignored.
  235. *
  236. * For example,
  237. *
  238. * @code
  239. * double intervals[] = { 0.0, 1.0, 2.0 };
  240. * double weights[] = { 0.0, 1.0, 0.0 };
  241. * piecewise_constant_distribution<> dist(
  242. * &intervals[0], &intervals[0] + 3, &weights[0]);
  243. * @endcode
  244. *
  245. * produces a triangle distribution.
  246. */
  247. template<class IntervalIter, class WeightIter>
  248. piecewise_linear_distribution(IntervalIter first_interval,
  249. IntervalIter last_interval,
  250. WeightIter first_weight)
  251. : _intervals(first_interval, last_interval)
  252. {
  253. if(_intervals.size() < 2) {
  254. default_init();
  255. } else {
  256. _weights.reserve(_intervals.size());
  257. for(std::size_t i = 0; i < _intervals.size(); ++i) {
  258. _weights.push_back(*first_weight++);
  259. }
  260. init();
  261. }
  262. }
  263. #ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST
  264. /**
  265. * Constructs a piecewise_linear_distribution from an
  266. * initializer_list containing the interval boundaries
  267. * and a unary function specifying the weights. Each
  268. * weight is determined by calling the function at the
  269. * corresponding interval boundary.
  270. *
  271. * If the initializer_list contains fewer than two elements,
  272. * this is equivalent to the default constructor and the
  273. * distribution will produce values uniformly distributed
  274. * in the range [0, 1).
  275. */
  276. template<class T, class F>
  277. piecewise_linear_distribution(std::initializer_list<T> il, F f)
  278. : _intervals(il.begin(), il.end())
  279. {
  280. if(_intervals.size() < 2) {
  281. default_init();
  282. } else {
  283. _weights.reserve(_intervals.size());
  284. for(typename std::vector<RealType>::const_iterator
  285. iter = _intervals.begin(), end = _intervals.end();
  286. iter != end; ++iter)
  287. {
  288. _weights.push_back(f(*iter));
  289. }
  290. init();
  291. }
  292. }
  293. #endif
  294. /**
  295. * Constructs a piecewise_linear_distribution from Boost.Range
  296. * ranges holding the interval boundaries and the weights. If
  297. * there are fewer than two interval boundaries, this is equivalent
  298. * to the default constructor and the distribution will produce
  299. * values uniformly distributed in the range [0, 1). The
  300. * number of weights must be equal to the number of
  301. * interval boundaries.
  302. */
  303. template<class IntervalsRange, class WeightsRange>
  304. piecewise_linear_distribution(const IntervalsRange& intervals_arg,
  305. const WeightsRange& weights_arg)
  306. : _intervals(boost::begin(intervals_arg), boost::end(intervals_arg)),
  307. _weights(boost::begin(weights_arg), boost::end(weights_arg))
  308. {
  309. if(_intervals.size() < 2) {
  310. default_init();
  311. } else {
  312. init();
  313. }
  314. }
  315. /**
  316. * Constructs a piecewise_linear_distribution that approximates a
  317. * function. The range of the distribution is [xmin, xmax). This
  318. * range is divided into nw equally sized intervals and the weights
  319. * are found by calling the unary function f on the interval boundaries.
  320. */
  321. template<class F>
  322. piecewise_linear_distribution(std::size_t nw,
  323. RealType xmin,
  324. RealType xmax,
  325. F f)
  326. {
  327. if(nw == 0) { nw = 1; }
  328. RealType delta = (xmax - xmin) / nw;
  329. _intervals.reserve(nw + 1);
  330. for(std::size_t i = 0; i < nw; ++i) {
  331. RealType x = xmin + i * delta;
  332. _intervals.push_back(x);
  333. _weights.push_back(f(x));
  334. }
  335. _intervals.push_back(xmax);
  336. _weights.push_back(f(xmax));
  337. init();
  338. }
  339. /**
  340. * Constructs a piecewise_linear_distribution from its parameters.
  341. */
  342. explicit piecewise_linear_distribution(const param_type& parm)
  343. : _intervals(parm._intervals),
  344. _weights(parm._weights)
  345. {
  346. init();
  347. }
  348. /**
  349. * Returns a value distributed according to the parameters of the
  350. * piecewise_linear_distribution.
  351. */
  352. template<class URNG>
  353. RealType operator()(URNG& urng) const
  354. {
  355. std::size_t i = _bins(urng);
  356. bool is_in_rectangle = (i % 2 == 0);
  357. i = i / 2;
  358. uniform_real<RealType> dist(_intervals[i], _intervals[i+1]);
  359. if(is_in_rectangle) {
  360. return dist(urng);
  361. } else if(_weights[i] < _weights[i+1]) {
  362. return (std::max)(dist(urng), dist(urng));
  363. } else {
  364. return (std::min)(dist(urng), dist(urng));
  365. }
  366. }
  367. /**
  368. * Returns a value distributed according to the parameters
  369. * specified by param.
  370. */
  371. template<class URNG>
  372. RealType operator()(URNG& urng, const param_type& parm) const
  373. {
  374. return piecewise_linear_distribution(parm)(urng);
  375. }
  376. /** Returns the smallest value that the distribution can produce. */
  377. result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () const
  378. { return _intervals.front(); }
  379. /** Returns the largest value that the distribution can produce. */
  380. result_type max BOOST_PREVENT_MACRO_SUBSTITUTION () const
  381. { return _intervals.back(); }
  382. /**
  383. * Returns a vector containing the probability densities
  384. * at the interval boundaries.
  385. */
  386. std::vector<RealType> densities() const
  387. {
  388. RealType sum = static_cast<RealType>(0);
  389. for(std::size_t i = 0; i < _intervals.size() - 1; ++i) {
  390. RealType width = _intervals[i + 1] - _intervals[i];
  391. sum += (_weights[i] + _weights[i + 1]) * width / 2;
  392. }
  393. std::vector<RealType> result;
  394. result.reserve(_weights.size());
  395. for(typename std::vector<RealType>::const_iterator
  396. iter = _weights.begin(), end = _weights.end();
  397. iter != end; ++iter)
  398. {
  399. result.push_back(*iter / sum);
  400. }
  401. return result;
  402. }
  403. /** Returns a vector containing the interval boundaries. */
  404. std::vector<RealType> intervals() const { return _intervals; }
  405. /** Returns the parameters of the distribution. */
  406. param_type param() const
  407. {
  408. return param_type(_intervals, _weights);
  409. }
  410. /** Sets the parameters of the distribution. */
  411. void param(const param_type& parm)
  412. {
  413. std::vector<RealType> new_intervals(parm._intervals);
  414. std::vector<RealType> new_weights(parm._weights);
  415. init(new_intervals, new_weights);
  416. _intervals.swap(new_intervals);
  417. _weights.swap(new_weights);
  418. }
  419. /**
  420. * Effects: Subsequent uses of the distribution do not depend
  421. * on values produced by any engine prior to invoking reset.
  422. */
  423. void reset() { _bins.reset(); }
  424. /** Writes a distribution to a @c std::ostream. */
  425. BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(
  426. os, piecewise_linear_distribution, pld)
  427. {
  428. os << pld.param();
  429. return os;
  430. }
  431. /** Reads a distribution from a @c std::istream */
  432. BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(
  433. is, piecewise_linear_distribution, pld)
  434. {
  435. param_type parm;
  436. if(is >> parm) {
  437. pld.param(parm);
  438. }
  439. return is;
  440. }
  441. /**
  442. * Returns true if the two distributions will return the
  443. * same sequence of values, when passed equal generators.
  444. */
  445. BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(
  446. piecewise_linear_distribution, lhs, rhs)
  447. {
  448. return lhs._intervals == rhs._intervals && lhs._weights == rhs._weights;
  449. }
  450. /**
  451. * Returns true if the two distributions may return different
  452. * sequences of values, when passed equal generators.
  453. */
  454. BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(piecewise_linear_distribution)
  455. private:
  456. /// @cond \show_private
  457. void init(const std::vector<RealType>& intervals_arg,
  458. const std::vector<RealType>& weights_arg)
  459. {
  460. using std::abs;
  461. std::vector<RealType> bin_weights;
  462. bin_weights.reserve((intervals_arg.size() - 1) * 2);
  463. for(std::size_t i = 0; i < intervals_arg.size() - 1; ++i) {
  464. RealType width = intervals_arg[i + 1] - intervals_arg[i];
  465. RealType w1 = weights_arg[i];
  466. RealType w2 = weights_arg[i + 1];
  467. bin_weights.push_back((std::min)(w1, w2) * width);
  468. bin_weights.push_back(abs(w1 - w2) * width / 2);
  469. }
  470. typedef discrete_distribution<std::size_t, RealType> bins_type;
  471. typename bins_type::param_type bins_param(bin_weights);
  472. _bins.param(bins_param);
  473. }
  474. void init()
  475. {
  476. init(_intervals, _weights);
  477. }
  478. void default_init()
  479. {
  480. _intervals.clear();
  481. _intervals.push_back(RealType(0));
  482. _intervals.push_back(RealType(1));
  483. _weights.clear();
  484. _weights.push_back(RealType(1));
  485. _weights.push_back(RealType(1));
  486. init();
  487. }
  488. discrete_distribution<std::size_t, RealType> _bins;
  489. std::vector<RealType> _intervals;
  490. std::vector<RealType> _weights;
  491. /// @endcond
  492. };
  493. }
  494. }
  495. #endif