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