File indexing completed on 2025-01-18 09:39:58
0001
0002
0003
0004
0005
0006
0007 #ifndef BOOST_MATH_QUADRATURE_NAIVE_MONTE_CARLO_HPP
0008 #define BOOST_MATH_QUADRATURE_NAIVE_MONTE_CARLO_HPP
0009 #include <sstream>
0010 #include <algorithm>
0011 #include <vector>
0012 #include <atomic>
0013 #include <memory>
0014 #include <functional>
0015 #include <future>
0016 #include <thread>
0017 #include <initializer_list>
0018 #include <utility>
0019 #include <random>
0020 #include <chrono>
0021 #include <map>
0022 #include <type_traits>
0023 #include <boost/math/policies/error_handling.hpp>
0024 #include <boost/math/special_functions/fpclassify.hpp>
0025
0026 #ifdef BOOST_NAIVE_MONTE_CARLO_DEBUG_FAILURES
0027 # include <iostream>
0028 #endif
0029
0030 namespace boost { namespace math { namespace quadrature {
0031
0032 namespace detail {
0033 enum class limit_classification {FINITE,
0034 LOWER_BOUND_INFINITE,
0035 UPPER_BOUND_INFINITE,
0036 DOUBLE_INFINITE};
0037 }
0038
0039 template<class Real, class F, class RandomNumberGenerator = std::mt19937_64, class Policy = boost::math::policies::policy<>,
0040 typename std::enable_if<std::is_trivially_copyable<Real>::value, bool>::type = true>
0041 class naive_monte_carlo
0042 {
0043 public:
0044 naive_monte_carlo(const F& integrand,
0045 std::vector<std::pair<Real, Real>> const & bounds,
0046 Real error_goal,
0047 bool singular = true,
0048 uint64_t threads = std::thread::hardware_concurrency(),
0049 uint64_t seed = 0) noexcept : m_num_threads{threads}, m_seed{seed}, m_volume(1)
0050 {
0051 using std::numeric_limits;
0052 using std::sqrt;
0053 using boost::math::isinf;
0054
0055 uint64_t n = bounds.size();
0056 m_lbs.resize(n);
0057 m_dxs.resize(n);
0058 m_limit_types.resize(n);
0059
0060 static const char* function = "boost::math::quadrature::naive_monte_carlo<%1%>";
0061 for (uint64_t i = 0; i < n; ++i)
0062 {
0063 if (bounds[i].second <= bounds[i].first)
0064 {
0065 boost::math::policies::raise_domain_error(function, "The upper bound is <= the lower bound.\n", bounds[i].second, Policy());
0066 return;
0067 }
0068 if (isinf(bounds[i].first))
0069 {
0070 if (isinf(bounds[i].second))
0071 {
0072 m_limit_types[i] = detail::limit_classification::DOUBLE_INFINITE;
0073 }
0074 else
0075 {
0076 m_limit_types[i] = detail::limit_classification::LOWER_BOUND_INFINITE;
0077
0078 m_lbs[i] = bounds[i].second;
0079 m_dxs[i] = numeric_limits<Real>::quiet_NaN();
0080 }
0081 }
0082 else if (isinf(bounds[i].second))
0083 {
0084 m_limit_types[i] = detail::limit_classification::UPPER_BOUND_INFINITE;
0085 if (singular)
0086 {
0087
0088
0089 m_lbs[i] = std::nextafter(bounds[i].first, (std::numeric_limits<Real>::max)());
0090 }
0091 else
0092 {
0093 m_lbs[i] = bounds[i].first;
0094 }
0095 m_dxs[i] = numeric_limits<Real>::quiet_NaN();
0096 }
0097 else
0098 {
0099 m_limit_types[i] = detail::limit_classification::FINITE;
0100 if (singular)
0101 {
0102 if (bounds[i].first == 0)
0103 {
0104 m_lbs[i] = std::numeric_limits<Real>::epsilon();
0105 }
0106 else
0107 {
0108 m_lbs[i] = std::nextafter(bounds[i].first, (std::numeric_limits<Real>::max)());
0109 }
0110
0111 m_dxs[i] = std::nextafter(bounds[i].second, std::numeric_limits<Real>::lowest()) - m_lbs[i];
0112 }
0113 else
0114 {
0115 m_lbs[i] = bounds[i].first;
0116 m_dxs[i] = bounds[i].second - bounds[i].first;
0117 }
0118 m_volume *= m_dxs[i];
0119 }
0120 }
0121
0122 m_integrand = [this, &integrand](std::vector<Real> & x)->Real
0123 {
0124 Real coeff = m_volume;
0125 for (uint64_t i = 0; i < x.size(); ++i)
0126 {
0127
0128
0129
0130 if (m_limit_types[i] == detail::limit_classification::FINITE)
0131 {
0132 x[i] = m_lbs[i] + x[i]*m_dxs[i];
0133 }
0134 else if (m_limit_types[i] == detail::limit_classification::UPPER_BOUND_INFINITE)
0135 {
0136 Real t = x[i];
0137 Real z = 1/(1 + numeric_limits<Real>::epsilon() - t);
0138 coeff *= (z*z)*(1 + numeric_limits<Real>::epsilon());
0139 x[i] = m_lbs[i] + t*z;
0140 }
0141 else if (m_limit_types[i] == detail::limit_classification::LOWER_BOUND_INFINITE)
0142 {
0143 Real t = x[i];
0144 Real z = 1/(t+sqrt((numeric_limits<Real>::min)()));
0145 coeff *= (z*z);
0146 x[i] = m_lbs[i] + (t-1)*z;
0147 }
0148 else
0149 {
0150 Real t1 = 1/(1+numeric_limits<Real>::epsilon() - x[i]);
0151 Real t2 = 1/(x[i]+numeric_limits<Real>::epsilon());
0152 x[i] = (2*x[i]-1)*t1*t2/4;
0153 coeff *= (t1*t1+t2*t2)/4;
0154 }
0155 }
0156 return coeff*integrand(x);
0157 };
0158
0159
0160
0161 std::vector<Real> x(m_lbs.size());
0162
0163
0164 if (seed == 0)
0165 {
0166 std::random_device rd;
0167 seed = rd();
0168 }
0169
0170 RandomNumberGenerator gen(seed);
0171 Real inv_denom = 1/static_cast<Real>(((gen.max)()-(gen.min)()));
0172
0173 m_num_threads = (std::max)(m_num_threads, static_cast<uint64_t>(1));
0174 m_thread_calls.reset(new std::atomic<uint64_t>[threads]);
0175 m_thread_Ss.reset(new std::atomic<Real>[threads]);
0176 m_thread_averages.reset(new std::atomic<Real>[threads]);
0177
0178 Real avg = 0;
0179 for (uint64_t i = 0; i < m_num_threads; ++i)
0180 {
0181 for (uint64_t j = 0; j < m_lbs.size(); ++j)
0182 {
0183 x[j] = (gen()-(gen.min)())*inv_denom;
0184 }
0185 Real y = m_integrand(x);
0186 m_thread_averages[i] = y;
0187 m_thread_calls[i] = 1;
0188 m_thread_Ss[i] = 0;
0189 avg += y;
0190 }
0191 avg /= m_num_threads;
0192 m_avg = avg;
0193
0194 m_error_goal = error_goal;
0195 m_start = std::chrono::system_clock::now();
0196 m_done = false;
0197 m_total_calls = m_num_threads;
0198 m_variance = (numeric_limits<Real>::max)();
0199 }
0200
0201 std::future<Real> integrate()
0202 {
0203
0204 m_done.store(false);
0205 m_start = std::chrono::system_clock::now();
0206 return std::async(std::launch::async,
0207 &naive_monte_carlo::m_integrate, this);
0208 }
0209
0210 void cancel()
0211 {
0212
0213
0214 m_seed = m_seed*m_seed;
0215 m_done = true;
0216
0217 m_error_goal = (std::numeric_limits<Real>::max)();
0218 }
0219
0220 Real variance() const
0221 {
0222 return m_variance.load();
0223 }
0224
0225 Real current_error_estimate() const
0226 {
0227 using std::sqrt;
0228
0229
0230
0231
0232 return sqrt(m_variance.load()/m_total_calls.load());
0233 }
0234
0235 std::chrono::duration<Real> estimated_time_to_completion() const
0236 {
0237 auto now = std::chrono::system_clock::now();
0238 std::chrono::duration<Real> elapsed_seconds = now - m_start;
0239 Real r = this->current_error_estimate()/m_error_goal.load();
0240 if (r*r <= 1) {
0241 return 0*elapsed_seconds;
0242 }
0243 return (r*r - 1)*elapsed_seconds;
0244 }
0245
0246 void update_target_error(Real new_target_error)
0247 {
0248 m_error_goal = new_target_error;
0249 }
0250
0251 Real progress() const
0252 {
0253 Real r = m_error_goal.load()/this->current_error_estimate();
0254 if (r*r >= 1)
0255 {
0256 return 1;
0257 }
0258 return r*r;
0259 }
0260
0261 Real current_estimate() const
0262 {
0263 return m_avg.load();
0264 }
0265
0266 uint64_t calls() const
0267 {
0268 return m_total_calls.load();
0269 }
0270
0271 private:
0272
0273 Real m_integrate()
0274 {
0275 uint64_t seed;
0276
0277 if (m_seed == 0)
0278 {
0279 std::random_device rd;
0280 seed = rd();
0281 }
0282 else
0283 {
0284 seed = m_seed;
0285 }
0286 RandomNumberGenerator gen(seed);
0287 int max_repeat_tries = 5;
0288 do{
0289
0290 if (max_repeat_tries < 5)
0291 {
0292 m_done = false;
0293
0294 #ifdef BOOST_NAIVE_MONTE_CARLO_DEBUG_FAILURES
0295 std::cerr << "Failed to achieve required tolerance first time through..\n";
0296 std::cerr << " variance = " << m_variance << std::endl;
0297 std::cerr << " average = " << m_avg << std::endl;
0298 std::cerr << " total calls = " << m_total_calls << std::endl;
0299
0300 for (std::size_t i = 0; i < m_num_threads; ++i)
0301 std::cerr << " thread_calls[" << i << "] = " << m_thread_calls[i] << std::endl;
0302 for (std::size_t i = 0; i < m_num_threads; ++i)
0303 std::cerr << " thread_averages[" << i << "] = " << m_thread_averages[i] << std::endl;
0304 for (std::size_t i = 0; i < m_num_threads; ++i)
0305 std::cerr << " thread_Ss[" << i << "] = " << m_thread_Ss[i] << std::endl;
0306 #endif
0307 }
0308
0309 std::vector<std::thread> threads(m_num_threads);
0310 for (uint64_t i = 0; i < threads.size(); ++i)
0311 {
0312 threads[i] = std::thread(&naive_monte_carlo::m_thread_monte, this, i, gen());
0313 }
0314 do {
0315 std::this_thread::sleep_for(std::chrono::milliseconds(100));
0316 uint64_t total_calls = 0;
0317 for (uint64_t i = 0; i < m_num_threads; ++i)
0318 {
0319 uint64_t t_calls = m_thread_calls[i].load(std::memory_order_consume);
0320 total_calls += t_calls;
0321 }
0322 Real variance = 0;
0323 Real avg = 0;
0324 for (uint64_t i = 0; i < m_num_threads; ++i)
0325 {
0326 uint64_t t_calls = m_thread_calls[i].load(std::memory_order_consume);
0327
0328 avg += m_thread_averages[i].load(std::memory_order_relaxed)*(static_cast<Real>(t_calls) / static_cast<Real>(total_calls));
0329 variance += m_thread_Ss[i].load(std::memory_order_relaxed);
0330 }
0331 m_avg.store(avg, std::memory_order_release);
0332 m_variance.store(variance / (total_calls - 1), std::memory_order_release);
0333 m_total_calls = total_calls;
0334
0335 if (m_done)
0336 {
0337 break;
0338 }
0339 } while (m_total_calls < 2048 || this->current_error_estimate() > m_error_goal.load(std::memory_order_consume));
0340
0341 m_done = true;
0342 std::for_each(threads.begin(), threads.end(),
0343 std::mem_fn(&std::thread::join));
0344 if (m_exception)
0345 {
0346 std::rethrow_exception(m_exception);
0347 }
0348
0349 uint64_t total_calls = 0;
0350 for (uint64_t i = 0; i < m_num_threads; ++i)
0351 {
0352 uint64_t t_calls = m_thread_calls[i].load(std::memory_order_consume);
0353 total_calls += t_calls;
0354 }
0355 Real variance = 0;
0356 Real avg = 0;
0357
0358 for (uint64_t i = 0; i < m_num_threads; ++i)
0359 {
0360 uint64_t t_calls = m_thread_calls[i].load(std::memory_order_consume);
0361
0362 avg += m_thread_averages[i].load(std::memory_order_relaxed)*(static_cast<Real>(t_calls) / static_cast<Real>(total_calls));
0363 variance += m_thread_Ss[i].load(std::memory_order_relaxed);
0364 }
0365 m_avg.store(avg, std::memory_order_release);
0366 m_variance.store(variance / (total_calls - 1), std::memory_order_release);
0367 m_total_calls = total_calls;
0368
0369
0370
0371
0372 }
0373 while ((--max_repeat_tries >= 0) && (this->current_error_estimate() > m_error_goal));
0374
0375 return m_avg.load(std::memory_order_consume);
0376 }
0377
0378 void m_thread_monte(uint64_t thread_index, uint64_t seed)
0379 {
0380 using std::numeric_limits;
0381 try
0382 {
0383 std::vector<Real> x(m_lbs.size());
0384 RandomNumberGenerator gen(seed);
0385 Real inv_denom = static_cast<Real>(1) / static_cast<Real>(( (gen.max)() - (gen.min)() ));
0386 Real M1 = m_thread_averages[thread_index].load(std::memory_order_consume);
0387 Real S = m_thread_Ss[thread_index].load(std::memory_order_consume);
0388
0389
0390
0391
0392 Real compensator = 0;
0393 uint64_t k = m_thread_calls[thread_index].load(std::memory_order_consume);
0394 while (!m_done)
0395 {
0396 int j = 0;
0397
0398
0399
0400
0401 int magic_calls_before_update = 2048;
0402 while (j++ < magic_calls_before_update)
0403 {
0404 for (uint64_t i = 0; i < m_lbs.size(); ++i)
0405 {
0406 x[i] = (gen() - (gen.min)())*inv_denom;
0407 }
0408 Real f = m_integrand(x);
0409 using std::isfinite;
0410 if (!isfinite(f))
0411 {
0412
0413 std::stringstream os;
0414 os << "Your integrand was evaluated at {";
0415 for (uint64_t i = 0; i < x.size() -1; ++i)
0416 {
0417 os << x[i] << ", ";
0418 }
0419 os << x[x.size() -1] << "}, and returned " << f << std::endl;
0420 static const char* function = "boost::math::quadrature::naive_monte_carlo<%1%>";
0421 boost::math::policies::raise_domain_error(function, os.str().c_str(), 7.2, Policy());
0422 }
0423 ++k;
0424 Real term = (f - M1)/k;
0425 Real y1 = term - compensator;
0426 Real M2 = M1 + y1;
0427 compensator = (M2 - M1) - y1;
0428 S += (f - M1)*(f - M2);
0429 M1 = M2;
0430 }
0431 m_thread_averages[thread_index].store(M1, std::memory_order_release);
0432 m_thread_Ss[thread_index].store(S, std::memory_order_release);
0433 m_thread_calls[thread_index].store(k, std::memory_order_release);
0434 }
0435 }
0436 catch (...)
0437 {
0438
0439 m_done = true;
0440 std::lock_guard<std::mutex> lock(m_exception_mutex);
0441 m_exception = std::current_exception();
0442 }
0443 }
0444
0445 std::function<Real(std::vector<Real> &)> m_integrand;
0446 uint64_t m_num_threads;
0447 std::atomic<uint64_t> m_seed;
0448 std::atomic<Real> m_error_goal;
0449 std::atomic<bool> m_done{};
0450 std::vector<Real> m_lbs;
0451 std::vector<Real> m_dxs;
0452 std::vector<detail::limit_classification> m_limit_types;
0453 Real m_volume;
0454 std::atomic<uint64_t> m_total_calls{};
0455
0456
0457 std::unique_ptr<std::atomic<uint64_t>[]> m_thread_calls;
0458 std::atomic<Real> m_variance;
0459 std::unique_ptr<std::atomic<Real>[]> m_thread_Ss;
0460 std::atomic<Real> m_avg;
0461 std::unique_ptr<std::atomic<Real>[]> m_thread_averages;
0462 std::chrono::time_point<std::chrono::system_clock> m_start;
0463 std::exception_ptr m_exception;
0464 std::mutex m_exception_mutex;
0465 };
0466
0467 }}}
0468 #endif