Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r63126 - in trunk: boost/random libs/random/doc libs/random/test
From: steven_at_[hidden]
Date: 2010-06-19 23:24:30


Author: steven_watanabe
Date: 2010-06-19 23:24:28 EDT (Sat, 19 Jun 2010)
New Revision: 63126
URL: http://svn.boost.org/trac/boost/changeset/63126

Log:
Better implementation of the poisson distribution. Fixes #1540.
Added:
   trunk/libs/random/test/test_poisson.cpp (contents, props changed)
   trunk/libs/random/test/test_poisson_distribution.cpp (contents, props changed)
Text files modified:
   trunk/boost/random/poisson_distribution.hpp | 377 ++++++++++++++++++++++++++++++++-------
   trunk/libs/random/doc/random.qbk | 2
   trunk/libs/random/test/Jamfile.v2 | 2
   3 files changed, 309 insertions(+), 72 deletions(-)

Modified: trunk/boost/random/poisson_distribution.hpp
==============================================================================
--- trunk/boost/random/poisson_distribution.hpp (original)
+++ trunk/boost/random/poisson_distribution.hpp 2010-06-19 23:24:28 EDT (Sat, 19 Jun 2010)
@@ -1,6 +1,7 @@
 /* boost random/poisson_distribution.hpp header file
  *
  * Copyright Jens Maurer 2002
+ * Copyright Steven Watanabe 2010
  * 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)
@@ -15,102 +16,336 @@
 #define BOOST_RANDOM_POISSON_DISTRIBUTION_HPP
 
 #include <boost/config/no_tr1/cmath.hpp>
+#include <cstdlib>
 #include <cassert>
-#include <iostream>
+#include <iosfwd>
 #include <boost/limits.hpp>
-#include <boost/static_assert.hpp>
+#include <boost/random/uniform_01.hpp>
 #include <boost/random/detail/config.hpp>
 
 namespace boost {
+namespace random {
 
-// Knuth
+namespace detail {
+
+template<class RealType>
+struct poisson_table {
+ static RealType value[10];
+};
+
+template<class RealType>
+RealType poisson_table<RealType>::value[10] = {
+ 0.0,
+ 0.0,
+ 0.69314718055994529,
+ 1.7917594692280550,
+ 3.1780538303479458,
+ 4.7874917427820458,
+ 6.5792512120101012,
+ 8.5251613610654147,
+ 10.604602902745251,
+ 12.801827480081469
+};
+
+}
 
 /**
  * An instantiation of the class template @c poisson_distribution is a
  * model of \random_distribution. The poisson distribution has
  * \f$p(i) = \frac{e^{-\lambda}\lambda^i}{i!}\f$
+ *
+ * This implementation is based on the PTRD algorithm described
+ *
+ * @blockquote
+ * "The transformed rejection method for generating Poisson random variables",
+ * Wolfgang Hormann, Insurance: Mathematics and Economics
+ * Volume 12, Issue 1, February 1993, Pages 39-45
+ * @endblockquote
  */
 template<class IntType = int, class RealType = double>
-class poisson_distribution
-{
+class poisson_distribution {
 public:
- typedef RealType input_type;
- typedef IntType result_type;
+ typedef IntType result_type;
+ typedef RealType input_type;
 
- /**
- * Constructs a @c poisson_distribution with the parameter @c mean.
- *
- * Requires: mean > 0
- */
- explicit poisson_distribution(const RealType& mean_arg = RealType(1))
- : _mean(mean_arg)
- {
-#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS
- // MSVC fails BOOST_STATIC_ASSERT with std::numeric_limits at class scope
- BOOST_STATIC_ASSERT(std::numeric_limits<IntType>::is_integer);
- BOOST_STATIC_ASSERT(!std::numeric_limits<RealType>::is_integer);
+ class param_type {
+ public:
+ typedef poisson_distribution distribution_type;
+ /**
+ * Construct a param_type object with the parameter "mean"
+ *
+ * Requires: mean > 0
+ */
+ explicit param_type(RealType mean_arg = RealType(1))
+ : _mean(mean_arg)
+ {
+ assert(_mean > 0);
+ }
+ /* Returns the "mean" parameter of the distribution. */
+ RealType mean() const { return _mean; }
+#ifndef BOOST_RANDOM_NO_STREAM_OPERATORS
+ /** Writes the parameters of the distribution to a @c std::ostream. */
+ template<class CharT, class Traits>
+ friend std::basic_ostream<CharT, Traits>&
+ operator<<(std::basic_ostream<CharT, Traits>& os,
+ const param_type& parm)
+ {
+ os << parm._mean;
+ return os;
+ }
+
+ /** Reads the parameters of the distribution from a @c std::istream. */
+ template<class CharT, class Traits>
+ friend std::basic_istream<CharT, Traits>&
+ operator>>(std::basic_istream<CharT, Traits>& is, param_type& parm)
+ {
+ is >> parm._mean;
+ return is;
+ }
 #endif
+ /** Returns true if the parameters have the same values. */
+ friend bool operator==(const param_type& lhs, const param_type& rhs)
+ {
+ return lhs._mean == rhs._mean;
+ }
+ /** Returns true if the parameters have different values. */
+ friend bool operator!=(const param_type& lhs, const param_type& rhs)
+ {
+ return !(lhs == rhs);
+ }
+ private:
+ RealType _mean;
+ };
+
+ /**
+ * Constructs a @c poisson_distribution with the parameter @c mean.
+ *
+ * Requires: mean > 0
+ */
+ explicit poisson_distribution(RealType mean_arg = RealType(1))
+ : _mean(mean_arg)
+ {
+ assert(_mean > 0);
+ init();
+ }
+
+ /**
+ * Construct an @c poisson_distribution object from the
+ * parameters.
+ */
+ explicit poisson_distribution(const param_type& parm)
+ : _mean(parm.mean())
+ {
+ init();
+ }
+
+ /**
+ * Returns a random variate distributed according to the
+ * poisson distribution.
+ */
+ template<class URNG>
+ IntType operator()(URNG& urng) const
+ {
+ if(use_inversion()) {
+ return invert(urng);
+ } else {
+ return generate(urng);
+ }
+ }
+
+ /**
+ * Returns a random variate distributed according to the
+ * poisson distribution with parameters specified by parm.
+ */
+ template<class URNG>
+ IntType operator()(URNG& urng, const param_type& parm) const
+ {
+ return poisson_distribution(parm)(urng);
+ }
 
- assert(_mean > RealType(0));
- init();
- }
-
- // compiler-generated copy ctor and assignment operator are fine
-
- /**
- * Returns: the "mean" parameter of the distribution.
- */
- RealType mean() const { return _mean; }
- void reset() { }
-
- template<class Engine>
- result_type operator()(Engine& eng)
- {
- // TODO: This is O(_mean), but it should be O(log(_mean)) for large _mean
- RealType product = RealType(1);
- for(result_type m = 0; ; ++m) {
- product *= eng();
- if(product <= _exp_mean)
- return m;
+ /** Returns the "mean" parameter of the distribution. */
+ RealType mean() const { return _mean; }
+
+ /** Returns the smallest value that the distribution can produce. */
+ IntType min BOOST_PREVENT_MACRO_SUBSTITUTION() const { return 0; }
+ /** Returns the largest value that the distribution can produce. */
+ IntType max BOOST_PREVENT_MACRO_SUBSTITUTION() const
+ { return (std::numeric_limits<IntType>::max)(); }
+
+ /** Returns the parameters of the distribution. */
+ param_type param() const { return param_type(_mean); }
+ /** Sets parameters of the distribution. */
+ void param(const param_type& parm)
+ {
+ _mean = parm.mean();
+ init();
     }
- }
+
+ /**
+ * Effects: Subsequent uses of the distribution do not depend
+ * on values produced by any engine prior to invoking reset.
+ */
+ void reset() { }
 
 #ifndef BOOST_RANDOM_NO_STREAM_OPERATORS
- template<class CharT, class Traits>
- friend std::basic_ostream<CharT,Traits>&
- operator<<(std::basic_ostream<CharT,Traits>& os, const poisson_distribution& pd)
- {
- os << pd._mean;
- return os;
- }
-
- template<class CharT, class Traits>
- friend std::basic_istream<CharT,Traits>&
- operator>>(std::basic_istream<CharT,Traits>& is, poisson_distribution& pd)
- {
- is >> std::ws >> pd._mean;
- pd.init();
- return is;
- }
+ /** Writes the parameters of the distribution to a @c std::ostream. */
+ template<class CharT, class Traits>
+ friend std::basic_ostream<CharT,Traits>&
+ operator<<(std::basic_ostream<CharT,Traits>& os,
+ const poisson_distribution& pd)
+ {
+ os << pd.param();
+ return os;
+ }
+
+ /** Reads the parameters of the distribution from a @c std::istream. */
+ template<class CharT, class Traits>
+ friend std::basic_istream<CharT,Traits>&
+ operator>>(std::basic_istream<CharT,Traits>& is, poisson_distribution& pd)
+ {
+ param_type parm;
+ if(is >> parm) {
+ pd.param(parm);
+ }
+ return is;
+ }
 #endif
+
+ /** Returns true if the two distributions will produce the same
+ sequence of values, given equal generators. */
+ friend bool operator==(const poisson_distribution& lhs,
+ const poisson_distribution& rhs)
+ {
+ return lhs._mean == rhs._mean;
+ }
+ /** Returns true if the two distributions could produce different
+ sequences of values, given equal generators. */
+ friend bool operator!=(const poisson_distribution& lhs,
+ const poisson_distribution& rhs)
+ {
+ return !(lhs == rhs);
+ }
 
 private:
- /// \cond hide_private_members
- void init()
- {
-#ifndef BOOST_NO_STDC_NAMESPACE
- // allow for Koenig lookup
- using std::exp;
-#endif
- _exp_mean = exp(-_mean);
- }
- /// \endcond
-
- RealType _mean;
- // some precomputed data from the parameters
- RealType _exp_mean;
+
+ /// @cond
+
+ bool use_inversion() const
+ {
+ return _mean < 10;
+ }
+
+ static RealType log_factorial(IntType k)
+ {
+ assert(k >= 0);
+ assert(k < 10);
+ return detail::poisson_table<RealType>::value[k];
+ }
+
+ void init()
+ {
+ using std::sqrt;
+ using std::exp;
+
+ if(use_inversion()) {
+ _exp_mean = exp(-_mean);
+ } else {
+ _ptrd.smu = sqrt(_mean);
+ _ptrd.b = 0.931 + 2.53 * _ptrd.smu;
+ _ptrd.a = -0.059 + 0.02483 * _ptrd.b;
+ _ptrd.inv_alpha = 1.1239 + 1.1328 / (_ptrd.b - 3.4);
+ _ptrd.v_r = 0.9277 - 3.6224 / (_ptrd.b - 2);
+ }
+ }
+
+ template<class URNG>
+ IntType generate(URNG& urng) const
+ {
+ using std::floor;
+ using std::abs;
+ using std::log;
+
+ while(true) {
+ RealType u;
+ RealType v = uniform_01<RealType>()(urng);
+ if(v <= 0.86 * _ptrd.v_r) {
+ u = v / _ptrd.v_r - 0.43;
+ return static_cast<IntType>(floor(
+ (2*_ptrd.a/(0.5-abs(u)) + _ptrd.b)*u + _mean + 0.445));
+ }
+
+ if(v >= _ptrd.v_r) {
+ u = uniform_01<RealType>()(urng) - 0.5;
+ } else {
+ u = v/_ptrd.v_r - 0.93;
+ u = ((u < 0)? -0.5 : 0.5) - u;
+ v = uniform_01<RealType>()(urng) * _ptrd.v_r;
+ }
+
+ RealType us = 0.5 - abs(u);
+ if(us < 0.013 && v > us) {
+ continue;
+ }
+
+ RealType k = floor((2*_ptrd.a/us + _ptrd.b)*u+_mean+0.445);
+ v = v*_ptrd.inv_alpha/(_ptrd.a/(us*us) + _ptrd.b);
+
+ RealType log_sqrt_2pi = 0.91893853320467267;
+
+ if(k >= 10) {
+ if(log(v*_ptrd.smu) <= (k + 0.5)*log(_mean/k)
+ - _mean
+ - log_sqrt_2pi
+ + k
+ - (1/12. - (1/360. - 1/(1260.*k*k))/(k*k))/k) {
+ return static_cast<IntType>(k);
+ }
+ } else if(k >= 0) {
+ if(log(v) <= k*log(_mean)
+ - _mean
+ - log_factorial(static_cast<IntType>(k))) {
+ return static_cast<IntType>(k);
+ }
+ }
+ }
+ }
+
+ template<class URNG>
+ IntType invert(URNG& urng) const
+ {
+ RealType p = _exp_mean;
+ IntType x = 0;
+ RealType u = uniform_01<RealType>()(urng);
+ while(u > p) {
+ u = u - p;
+ ++x;
+ p = _mean * p / x;
+ }
+ return x;
+ }
+
+ RealType _mean;
+
+ union {
+ // for ptrd
+ struct {
+ RealType v_r;
+ RealType a;
+ RealType b;
+ RealType smu;
+ RealType inv_alpha;
+ } _ptrd;
+ // for inversion
+ RealType _exp_mean;
+ };
+
+ /// @endcond
 };
 
+} // namespace random
+
+using random::poisson_distribution;
+
 } // namespace boost
 
 #endif // BOOST_RANDOM_POISSON_DISTRIBUTION_HPP

Modified: trunk/libs/random/doc/random.qbk
==============================================================================
--- trunk/libs/random/doc/random.qbk (original)
+++ trunk/libs/random/doc/random.qbk 2010-06-19 23:24:28 EDT (Sat, 19 Jun 2010)
@@ -65,7 +65,7 @@
 [def __binomial_distribution [classref boost::random::binomial_distribution binomial_distribution]]
 [def __cauchy_distribution [classref boost::cauchy_distribution cauchy_distribution]]
 [def __gamma_distribution [classref boost::gamma_distribution gamma_distribution]]
-[def __poisson_distribution [classref boost::poisson_distribution poisson_distribution]]
+[def __poisson_distribution [classref boost::random::poisson_distribution poisson_distribution]]
 [def __geometric_distribution [classref boost::geometric_distribution geometric_distribution]]
 [def __triangle_distribution [classref boost::triangle_distribution triangle_distribution]]
 [def __exponential_distribution [classref boost::exponential_distribution exponential_distribution]]

Modified: trunk/libs/random/test/Jamfile.v2
==============================================================================
--- trunk/libs/random/test/Jamfile.v2 (original)
+++ trunk/libs/random/test/Jamfile.v2 2010-06-19 23:24:28 EDT (Sat, 19 Jun 2010)
@@ -43,6 +43,8 @@
 
 run test_binomial.cpp ;
 run test_binomial_distribution.cpp /boost//unit_test_framework ;
+run test_poisson.cpp ;
+run test_poisson_distribution.cpp /boost//unit_test_framework ;
 
 # run nondet_random_speed.cpp ;
 # run random_device.cpp ;

Added: trunk/libs/random/test/test_poisson.cpp
==============================================================================
--- (empty file)
+++ trunk/libs/random/test/test_poisson.cpp 2010-06-19 23:24:28 EDT (Sat, 19 Jun 2010)
@@ -0,0 +1,122 @@
+/* test_poisson.cpp
+ *
+ * Copyright Steven Watanabe 2010
+ * 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)
+ *
+ * $Id$
+ *
+ */
+
+#include <boost/random/poisson_distribution.hpp>
+#include <boost/random/uniform_real.hpp>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/math/distributions/poisson.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/exception/diagnostic_information.hpp>
+#include <vector>
+#include <iostream>
+#include <numeric>
+
+#include "chi_squared_test.hpp"
+
+bool do_test(double mean, long long max) {
+ std::cout << "running poisson(" << mean << ")" << " " << max << " times: " << std::flush;
+
+ int max_value = static_cast<int>(mean * 4);
+ std::vector<double> expected(max_value+1);
+ {
+ boost::math::poisson dist(mean);
+ for(int i = 0; i <= max_value; ++i) {
+ expected[i] = pdf(dist, i);
+ }
+ expected.back() += 1 - cdf(dist, max_value);
+ }
+
+ boost::random::poisson_distribution<int, double> dist(mean);
+ boost::mt19937 gen;
+ std::vector<long long> results(max_value + 1);
+ for(long long i = 0; i < max; ++i) {
+ ++results[std::min(dist(gen), max_value)];
+ }
+
+ long long sum = std::accumulate(results.begin(), results.end(), 0ll);
+ if(sum != max) {
+ std::cout << "*** Failed: incorrect total: " << sum << " ***" << std::endl;
+ return false;
+ }
+ double chsqr = chi_squared_test(results, expected, max);
+
+ bool result = chsqr < 0.99;
+ const char* err = result? "" : "*";
+ std::cout << std::setprecision(17) << chsqr << err << std::endl;
+
+ std::cout << std::setprecision(6);
+
+ return result;
+}
+
+bool do_tests(int repeat, double max_mean, long long trials) {
+ boost::mt19937 gen;
+ boost::uniform_real<> rdist(1e-15, max_mean);
+ int errors = 0;
+ for(int i = 0; i < repeat; ++i) {
+ if(!do_test(rdist(gen), trials)) {
+ ++errors;
+ }
+ }
+ if(errors != 0) {
+ std::cout << "*** " << errors << " errors detected ***" << std::endl;
+ }
+ return errors == 0;
+}
+
+int usage() {
+ std::cerr << "Usage: test_poisson -r <repeat> -m <max mean> -t <trials>" << std::endl;
+ return 2;
+}
+
+template<class T>
+bool handle_option(int& argc, char**& argv, char opt, T& value) {
+ if(argv[0][1] == opt && argc > 1) {
+ --argc;
+ ++argv;
+ value = boost::lexical_cast<T>(argv[0]);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int main(int argc, char** argv) {
+ int repeat = 10;
+ double max_mean = 100000;
+ long long trials = 1000000ll;
+
+ if(argc > 0) {
+ --argc;
+ ++argv;
+ }
+ while(argc > 0) {
+ if(argv[0][0] != '-') return usage();
+ else if(!handle_option(argc, argv, 'r', repeat)
+ && !handle_option(argc, argv, 'm', max_mean)
+ && !handle_option(argc, argv, 't', trials)) {
+ return usage();
+ }
+ --argc;
+ ++argv;
+ }
+
+ try {
+ if(do_tests(repeat, max_mean, trials)) {
+ return 0;
+ } else {
+ return EXIT_FAILURE;
+ }
+ } catch(...) {
+ std::cerr << boost::current_exception_diagnostic_information() << std::endl;
+ return EXIT_FAILURE;
+ }
+}

Added: trunk/libs/random/test/test_poisson_distribution.cpp
==============================================================================
--- (empty file)
+++ trunk/libs/random/test/test_poisson_distribution.cpp 2010-06-19 23:24:28 EDT (Sat, 19 Jun 2010)
@@ -0,0 +1,104 @@
+/* test_poisson_distribution.cpp
+ *
+ * Copyright Steven Watanabe 2010
+ * 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)
+ *
+ * $Id$
+ *
+ */
+
+#include <boost/random/poisson_distribution.hpp>
+#include <boost/random/linear_congruential.hpp>
+#include <sstream>
+
+#define BOOST_TEST_MAIN
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(test_constructors) {
+ boost::random::poisson_distribution<> dist;
+ BOOST_CHECK_EQUAL(dist.mean(), 1.0);
+ boost::random::poisson_distribution<> dist_one(7.5);
+ BOOST_CHECK_EQUAL(dist_one.mean(), 7.5);
+ boost::random::poisson_distribution<> copy(dist);
+ BOOST_CHECK_EQUAL(dist, copy);
+ boost::random::poisson_distribution<> copy_one(dist_one);
+ BOOST_CHECK_EQUAL(dist_one, copy_one);
+}
+
+BOOST_AUTO_TEST_CASE(test_param) {
+ boost::random::poisson_distribution<> dist(7.5);
+ boost::random::poisson_distribution<>::param_type param = dist.param();
+ BOOST_CHECK_EQUAL(param.mean(), 7.5);
+ boost::random::poisson_distribution<> copy1(param);
+ BOOST_CHECK_EQUAL(dist, copy1);
+ boost::random::poisson_distribution<> copy2;
+ copy2.param(param);
+ BOOST_CHECK_EQUAL(dist, copy2);
+
+ boost::random::poisson_distribution<>::param_type param_copy = param;
+ BOOST_CHECK_EQUAL(param, param_copy);
+ BOOST_CHECK(param == param_copy);
+ BOOST_CHECK(!(param != param_copy));
+ boost::random::poisson_distribution<>::param_type param_default;
+ BOOST_CHECK_EQUAL(param_default.mean(), 1.0);
+ BOOST_CHECK(param != param_default);
+ BOOST_CHECK(!(param == param_default));
+ boost::random::poisson_distribution<>::param_type param_one(7.5);
+ BOOST_CHECK_EQUAL(param_one.mean(), 7.5);
+ BOOST_CHECK(param_default != param_one);
+ BOOST_CHECK(!(param_default == param_one));
+}
+
+BOOST_AUTO_TEST_CASE(test_min_max) {
+ boost::random::poisson_distribution<> dist;
+ BOOST_CHECK_EQUAL((dist.min)(), 0);
+ BOOST_CHECK_EQUAL((dist.max)(), (std::numeric_limits<int>::max)());
+ boost::random::poisson_distribution<> dist_one(7.5);
+ BOOST_CHECK_EQUAL((dist_one.min)(), 0);
+ BOOST_CHECK_EQUAL((dist_one.max)(), (std::numeric_limits<int>::max)());
+}
+
+BOOST_AUTO_TEST_CASE(test_comparison) {
+ boost::random::poisson_distribution<> dist;
+ boost::random::poisson_distribution<> dist_copy(dist);
+ boost::random::poisson_distribution<> dist_one(7.5);
+ boost::random::poisson_distribution<> dist_one_copy(dist_one);
+ BOOST_CHECK(dist == dist_copy);
+ BOOST_CHECK(!(dist != dist_copy));
+ BOOST_CHECK(dist_one == dist_one_copy);
+ BOOST_CHECK(!(dist_one != dist_one_copy));
+ BOOST_CHECK(dist != dist_one);
+ BOOST_CHECK(!(dist == dist_one));
+}
+
+BOOST_AUTO_TEST_CASE(test_streaming) {
+ boost::random::poisson_distribution<> dist(7.5);
+ std::stringstream stream;
+ stream << dist;
+ boost::random::poisson_distribution<> restored_dist;
+ stream >> restored_dist;
+ BOOST_CHECK_EQUAL(dist, restored_dist);
+}
+
+BOOST_AUTO_TEST_CASE(test_generation) {
+ boost::minstd_rand0 gen;
+ boost::random::poisson_distribution<> dist;
+ boost::random::poisson_distribution<> dist_1000(1000);
+ for(int i = 0; i < 10; ++i) {
+ // Basic sanity checks. Technically these tests are incorrect,
+ // since the range of a poisson_distribution is [0, inf), but
+ // the probability that there's an error is very small.
+ int value = dist(gen);
+ BOOST_CHECK_GE(value, 0);
+ BOOST_CHECK_LE(value, 10);
+ int value_two = dist_1000(gen);
+ BOOST_CHECK_GE(value_two, 10);
+ int value_param = dist_1000(gen, dist.param());
+ BOOST_CHECK_GE(value_param, 0);
+ BOOST_CHECK_LE(value_param, 10);
+ int value_two_param = dist(gen, dist_1000.param());
+ BOOST_CHECK_GE(value_two_param, 10);
+ }
+}


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk