1 /* test_hyperexponential.cpp
2  *
3  * Copyright Marco Guazzone 2014
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  * \author Marco Guazzone (marco.guazzone@gmail.com)
9  *
10  */
11 
12 #include <boost/exception/diagnostic_information.hpp>
13 #include <boost/lexical_cast.hpp>
14 #include <boost/math/distributions/hyperexponential.hpp>
15 #include <boost/numeric/conversion/cast.hpp>
16 #include <boost/random/hyperexponential_distribution.hpp>
17 #include <boost/random/mersenne_twister.hpp>
18 #include <boost/random/uniform_int.hpp>
19 #include <boost/random/uniform_real.hpp>
20 #include <boost/range/numeric.hpp>
21 #include <cstring>
22 #include <iostream>
23 #include <numeric>
24 #include <vector>
25 
26 #include "statistic_tests.hpp"
27 
28 
29 // This test unit is similar to the one in "test_real_distribution.ipp", which
30 // has been changed for the hyperexponential distribution.
31 // We cannot directly use the original test suite since it doesn't work for
32 // distributions with vector parameters.
33 // Also, this implementation has been inspired by the test unit for the
34 // discrete_distribution class.
35 
36 
37 #ifndef BOOST_RANDOM_P_CUTOFF
38 # define BOOST_RANDOM_P_CUTOFF 0.99
39 #endif
40 
41 #define BOOST_RANDOM_HYPEREXP_NUM_PHASES_MIN 1
42 #define BOOST_RANDOM_HYPEREXP_NUM_PHASES_MAX 3
43 #define BOOST_RANDOM_HYPEREXP_PROBABILITY_MIN 0.01
44 #define BOOST_RANDOM_HYPEREXP_PROBABILITY_MAX 1.0
45 #define BOOST_RANDOM_HYPEREXP_RATE_MIN 0.0001
46 #define BOOST_RANDOM_HYPEREXP_RATE_MAX 1000.0
47 #define BOOST_RANDOM_HYPEREXP_NUM_TRIALS 1000000ll
48 
49 
50 namespace /*<unnamed>*/ { namespace detail {
51 
52 template <typename T>
normalize(std::vector<T> & v)53 std::vector<T>& normalize(std::vector<T>& v)
54 {
55 	if (v.size() == 0)
56 	{
57 		return v;
58 	}
59 
60     const T sum = std::accumulate(v.begin(), v.end(), static_cast<T>(0));
61 	T final_sum = 0;
62 
63     const typename std::vector<T>::iterator end = --v.end();
64     for (typename std::vector<T>::iterator it = v.begin();
65          it != end;
66          ++it)
67     {
68         *it /= sum;
69     }
70 	*end = 1 - final_sum;  // avoids round off errors, ensures the probs really do sum to 1.
71 
72     return v;
73 }
74 
75 template <typename T>
normalize_copy(std::vector<T> const & v)76 std::vector<T> normalize_copy(std::vector<T> const& v)
77 {
78 	std::vector<T> vv(v);
79 
80     return normalize(vv);
81 }
82 
83 template <typename T, typename DistT, typename EngineT>
make_random_vector(std::size_t n,DistT const & dist,EngineT & eng)84 std::vector<T> make_random_vector(std::size_t n, DistT const& dist, EngineT& eng)
85 {
86     std::vector<T> res(n);
87 
88     for (std::size_t i = 0; i < n; ++i)
89     {
90         res[i] = dist(eng);
91     }
92 
93     return res;
94 }
95 
96 }} // Namespace <unnamed>::detail
97 
98 
do_test(std::vector<double> const & probabilities,std::vector<double> const & rates,long long max,boost::mt19937 & gen)99 bool do_test(std::vector<double> const& probabilities,
100              std::vector<double> const& rates,
101              long long max,
102              boost::mt19937& gen)
103 {
104     std::cout << "running hyperexponential(probabilities,rates) " << max << " times: " << std::flush;
105 
106     boost::math::hyperexponential_distribution<> expected(probabilities, rates);
107 
108     boost::random::hyperexponential_distribution<> dist(probabilities, rates);
109 
110     kolmogorov_experiment test(boost::numeric_cast<int>(max));
111     boost::variate_generator<boost::mt19937&, boost::random::hyperexponential_distribution<> > vgen(gen, dist);
112 
113     const double prob = test.probability(test.run(vgen, expected));
114 
115     const bool result = prob < BOOST_RANDOM_P_CUTOFF;
116     const char* err = result? "" : "*";
117     std::cout << std::setprecision(17) << prob << err << std::endl;
118 
119     std::cout << std::setprecision(6);
120 
121     return result;
122 }
123 
do_tests(int repeat,int max_num_phases,double max_rate,long long trials)124 bool do_tests(int repeat, int max_num_phases, double max_rate, long long trials)
125 {
126     boost::mt19937 gen;
127     boost::random::uniform_int_distribution<> num_phases_dist(BOOST_RANDOM_HYPEREXP_NUM_PHASES_MIN, max_num_phases);
128 
129     int errors = 0;
130     for (int i = 0; i < repeat; ++i)
131     {
132         const int num_phases = num_phases_dist(gen);
133         boost::random::uniform_real_distribution<> prob_dist(BOOST_RANDOM_HYPEREXP_PROBABILITY_MIN, BOOST_RANDOM_HYPEREXP_PROBABILITY_MAX);
134         boost::random::uniform_real_distribution<> rate_dist(BOOST_RANDOM_HYPEREXP_RATE_MIN, max_rate);
135 
136         const std::vector<double> probabilities = detail::normalize_copy(detail::make_random_vector<double>(num_phases, prob_dist, gen));
137         const std::vector<double> rates = detail::make_random_vector<double>(num_phases, rate_dist, gen);
138 
139         if (!do_test(probabilities, rates, trials, gen))
140         {
141             ++errors;
142         }
143     }
144     if (errors != 0)
145     {
146         std::cout << "*** " << errors << " errors detected ***" << std::endl;
147     }
148 
149     return errors == 0;
150 }
151 
usage()152 int usage()
153 {
154     std::cerr << "Usage: test_hyperexponential"
155         " -r <repeat>"
156         " -num_phases"
157         " <max num_phases>"
158         " -rate"
159         " <max rate>"
160         " -t <trials>" << std::endl;
161     return 2;
162 }
163 
164 template<class T>
handle_option(int & argc,char ** & argv,const char * opt,T & value)165 bool handle_option(int& argc, char**& argv, const char* opt, T& value)
166 {
167     if (std::strcmp(argv[0], opt) == 0 && argc > 1)
168     {
169         --argc;
170         ++argv;
171         value = boost::lexical_cast<T>(argv[0]);
172         return true;
173     }
174     else
175     {
176         return false;
177     }
178 }
179 
main(int argc,char ** argv)180 int main(int argc, char** argv)
181 {
182     int repeat = 1;
183     int max_num_phases = BOOST_RANDOM_HYPEREXP_NUM_PHASES_MAX;
184     double max_rate = BOOST_RANDOM_HYPEREXP_RATE_MAX;
185     long long trials = BOOST_RANDOM_HYPEREXP_NUM_TRIALS;
186 
187     if (argc > 0)
188     {
189         --argc;
190         ++argv;
191     }
192     while(argc > 0)
193     {
194         if (argv[0][0] != '-')
195         {
196             return usage();
197         }
198         else if (!handle_option(argc, argv, "-r", repeat)
199                  && !handle_option(argc, argv, "-num_phases", max_num_phases)
200                  && !handle_option(argc, argv, "-rate", max_rate)
201                  && !handle_option(argc, argv, "-t", trials))
202         {
203             return usage();
204         }
205         --argc;
206         ++argv;
207     }
208 
209     try
210     {
211         if (do_tests(repeat, max_num_phases, max_rate, trials))
212         {
213             return 0;
214         }
215         else
216         {
217             return EXIT_FAILURE;
218         }
219     }
220     catch(...)
221     {
222         std::cerr << boost::current_exception_diagnostic_information() << std::endl;
223         return EXIT_FAILURE;
224     }
225 }
226