ooura_fourier_integrals_detail.hpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. // Copyright Nick Thompson, 2019
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0.
  4. // (See accompanying file LICENSE_1_0.txt
  5. // or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. #ifndef BOOST_MATH_QUADRATURE_DETAIL_OOURA_FOURIER_INTEGRALS_DETAIL_HPP
  7. #define BOOST_MATH_QUADRATURE_DETAIL_OOURA_FOURIER_INTEGRALS_DETAIL_HPP
  8. #include <utility> // for std::pair.
  9. #include <vector>
  10. #include <iostream>
  11. #include <boost/math/special_functions/expm1.hpp>
  12. #include <boost/math/special_functions/sin_pi.hpp>
  13. #include <boost/math/special_functions/cos_pi.hpp>
  14. #include <boost/math/constants/constants.hpp>
  15. #include <boost/math/tools/config.hpp>
  16. #ifdef BOOST_MATH_HAS_THREADS
  17. #include <mutex>
  18. #include <atomic>
  19. #endif
  20. namespace boost { namespace math { namespace quadrature { namespace detail {
  21. // Ooura and Mori, A robust double exponential formula for Fourier-type integrals,
  22. // eta is the argument to the exponential in equation 3.3:
  23. template<class Real>
  24. std::pair<Real, Real> ooura_eta(Real x, Real alpha) {
  25. using std::expm1;
  26. using std::exp;
  27. using std::abs;
  28. Real expx = exp(x);
  29. Real eta_prime = 2 + alpha/expx + expx/4;
  30. Real eta;
  31. // This is the fast branch:
  32. if (abs(x) > 0.125) {
  33. eta = 2*x - alpha*(1/expx - 1) + (expx - 1)/4;
  34. }
  35. else {// this is the slow branch using expm1 for small x:
  36. eta = 2*x - alpha*expm1(-x) + expm1(x)/4;
  37. }
  38. return {eta, eta_prime};
  39. }
  40. // Ooura and Mori, A robust double exponential formula for Fourier-type integrals,
  41. // equation 3.6:
  42. template<class Real>
  43. Real calculate_ooura_alpha(Real h)
  44. {
  45. using boost::math::constants::pi;
  46. using std::log1p;
  47. using std::sqrt;
  48. Real x = sqrt(16 + 4*log1p(pi<Real>()/h)/h);
  49. return 1/x;
  50. }
  51. template<class Real>
  52. std::pair<Real, Real> ooura_sin_node_and_weight(long n, Real h, Real alpha)
  53. {
  54. using std::expm1;
  55. using std::exp;
  56. using std::abs;
  57. using boost::math::constants::pi;
  58. using std::isnan;
  59. if (n == 0) {
  60. // Equation 44 of https://arxiv.org/pdf/0911.4796.pdf
  61. // Fourier Transform of the Stretched Exponential Function: Analytic Error Bounds,
  62. // Double Exponential Transform, and Open-Source Implementation,
  63. // Joachim Wuttke,
  64. // The C library libkww provides functions to compute the Kohlrausch-Williams-Watts function,
  65. // the Laplace-Fourier transform of the stretched (or compressed) exponential function exp(-t^beta)
  66. // for exponent beta between 0.1 and 1.9 with sixteen decimal digits accuracy.
  67. Real eta_prime_0 = Real(2) + alpha + Real(1)/Real(4);
  68. Real node = pi<Real>()/(eta_prime_0*h);
  69. Real weight = pi<Real>()*boost::math::sin_pi(1/(eta_prime_0*h));
  70. Real eta_dbl_prime = -alpha + Real(1)/Real(4);
  71. Real phi_prime_0 = (1 - eta_dbl_prime/(eta_prime_0*eta_prime_0))/2;
  72. weight *= phi_prime_0;
  73. return {node, weight};
  74. }
  75. Real x = n*h;
  76. auto p = ooura_eta(x, alpha);
  77. auto eta = p.first;
  78. auto eta_prime = p.second;
  79. Real expm1_meta = expm1(-eta);
  80. Real exp_meta = exp(-eta);
  81. Real node = -n*pi<Real>()/expm1_meta;
  82. // I have verified that this is not a significant source of inaccuracy in the weight computation:
  83. Real phi_prime = -(expm1_meta + x*exp_meta*eta_prime)/(expm1_meta*expm1_meta);
  84. // The main source of inaccuracy is in computation of sin_pi.
  85. // But I've agonized over this, and I think it's as good as it can get:
  86. Real s = pi<Real>();
  87. Real arg;
  88. if(eta > 1) {
  89. arg = n/( 1/exp_meta - 1 );
  90. s *= boost::math::sin_pi(arg);
  91. if (n&1) {
  92. s *= -1;
  93. }
  94. }
  95. else if (eta < -1) {
  96. arg = n/(1-exp_meta);
  97. s *= boost::math::sin_pi(arg);
  98. }
  99. else {
  100. arg = -n*exp_meta/expm1_meta;
  101. s *= boost::math::sin_pi(arg);
  102. if (n&1) {
  103. s *= -1;
  104. }
  105. }
  106. Real weight = s*phi_prime;
  107. return {node, weight};
  108. }
  109. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  110. template<class Real>
  111. void print_ooura_estimate(size_t i, Real I0, Real I1, Real omega) {
  112. using std::abs;
  113. std::cout << std::defaultfloat
  114. << std::setprecision(std::numeric_limits<Real>::digits10)
  115. << std::fixed;
  116. std::cout << "h = " << Real(1)/Real(1<<i) << ", I_h = " << I0/omega
  117. << " = " << std::hexfloat << I0/omega << ", absolute error estimate = "
  118. << std::defaultfloat << std::scientific << abs(I0-I1) << std::endl;
  119. }
  120. #endif
  121. template<class Real>
  122. std::pair<Real, Real> ooura_cos_node_and_weight(long n, Real h, Real alpha)
  123. {
  124. using std::expm1;
  125. using std::exp;
  126. using std::abs;
  127. using boost::math::constants::pi;
  128. Real x = h*(n-Real(1)/Real(2));
  129. auto p = ooura_eta(x, alpha);
  130. auto eta = p.first;
  131. auto eta_prime = p.second;
  132. Real expm1_meta = expm1(-eta);
  133. Real exp_meta = exp(-eta);
  134. Real node = pi<Real>()*(Real(1)/Real(2)-n)/expm1_meta;
  135. Real phi_prime = -(expm1_meta + x*exp_meta*eta_prime)/(expm1_meta*expm1_meta);
  136. // Takuya Ooura and Masatake Mori,
  137. // Journal of Computational and Applied Mathematics, 112 (1999) 229-241.
  138. // A robust double exponential formula for Fourier-type integrals.
  139. // Equation 4.6
  140. Real s = pi<Real>();
  141. Real arg;
  142. if (eta < -1) {
  143. arg = -(n-Real(1)/Real(2))/expm1_meta;
  144. s *= boost::math::cos_pi(arg);
  145. }
  146. else {
  147. arg = -(n-Real(1)/Real(2))*exp_meta/expm1_meta;
  148. s *= boost::math::sin_pi(arg);
  149. if (n&1) {
  150. s *= -1;
  151. }
  152. }
  153. Real weight = s*phi_prime;
  154. return {node, weight};
  155. }
  156. template<class Real>
  157. class ooura_fourier_sin_detail {
  158. public:
  159. ooura_fourier_sin_detail(const Real relative_error_goal, size_t levels) {
  160. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  161. std::cout << "ooura_fourier_sin with relative error goal " << relative_error_goal
  162. << " & " << levels << " levels." << std::endl;
  163. #endif // BOOST_MATH_INSTRUMENT_OOURA
  164. if (relative_error_goal < std::numeric_limits<Real>::epsilon() * 2) {
  165. throw std::domain_error("The relative error goal cannot be smaller than the unit roundoff.");
  166. }
  167. using std::abs;
  168. requested_levels_ = levels;
  169. starting_level_ = 0;
  170. rel_err_goal_ = relative_error_goal;
  171. big_nodes_.reserve(levels);
  172. bweights_.reserve(levels);
  173. little_nodes_.reserve(levels);
  174. lweights_.reserve(levels);
  175. for (size_t i = 0; i < levels; ++i) {
  176. if (std::is_same<Real, float>::value) {
  177. add_level<double>(i);
  178. }
  179. else if (std::is_same<Real, double>::value) {
  180. add_level<long double>(i);
  181. }
  182. else {
  183. add_level<Real>(i);
  184. }
  185. }
  186. }
  187. std::vector<std::vector<Real>> const & big_nodes() const {
  188. return big_nodes_;
  189. }
  190. std::vector<std::vector<Real>> const & weights_for_big_nodes() const {
  191. return bweights_;
  192. }
  193. std::vector<std::vector<Real>> const & little_nodes() const {
  194. return little_nodes_;
  195. }
  196. std::vector<std::vector<Real>> const & weights_for_little_nodes() const {
  197. return lweights_;
  198. }
  199. template<class F>
  200. std::pair<Real,Real> integrate(F const & f, Real omega) {
  201. using std::abs;
  202. using std::max;
  203. using boost::math::constants::pi;
  204. if (omega == 0) {
  205. return {Real(0), Real(0)};
  206. }
  207. if (omega < 0) {
  208. auto p = this->integrate(f, -omega);
  209. return {-p.first, p.second};
  210. }
  211. Real I1 = std::numeric_limits<Real>::quiet_NaN();
  212. Real relative_error_estimate = std::numeric_limits<Real>::quiet_NaN();
  213. // As we compute integrals, we learn about their structure.
  214. // Assuming we compute f(t)sin(wt) for many different omega, this gives some
  215. // a posteriori ability to choose a refinement level that is roughly appropriate.
  216. size_t i = starting_level_;
  217. do {
  218. Real I0 = estimate_integral(f, omega, i);
  219. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  220. print_ooura_estimate(i, I0, I1, omega);
  221. #endif
  222. Real absolute_error_estimate = abs(I0-I1);
  223. Real scale = (max)(abs(I0), abs(I1));
  224. if (!isnan(I1) && absolute_error_estimate <= rel_err_goal_*scale) {
  225. starting_level_ = (max)(long(i) - 1, long(0));
  226. return {I0/omega, absolute_error_estimate/scale};
  227. }
  228. I1 = I0;
  229. } while(++i < big_nodes_.size());
  230. // We've used up all our precomputed levels.
  231. // Now we need to add more.
  232. // It might seems reasonable to just keep adding levels indefinitely, if that's what the user wants.
  233. // But in fact the nodes and weights just merge into each other and the error gets worse after a certain number.
  234. // This value for max_additional_levels was chosen by observation of a slowly converging oscillatory integral:
  235. // f(x) := cos(7cos(x))sin(x)/x
  236. size_t max_additional_levels = 4;
  237. while (big_nodes_.size() < requested_levels_ + max_additional_levels) {
  238. size_t ii = big_nodes_.size();
  239. if (std::is_same<Real, float>::value) {
  240. add_level<double>(ii);
  241. }
  242. else if (std::is_same<Real, double>::value) {
  243. add_level<long double>(ii);
  244. }
  245. else {
  246. add_level<Real>(ii);
  247. }
  248. Real I0 = estimate_integral(f, omega, ii);
  249. Real absolute_error_estimate = abs(I0-I1);
  250. Real scale = (max)(abs(I0), abs(I1));
  251. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  252. print_ooura_estimate(ii, I0, I1, omega);
  253. #endif
  254. if (absolute_error_estimate <= rel_err_goal_*scale) {
  255. starting_level_ = (max)(long(ii) - 1, long(0));
  256. return {I0/omega, absolute_error_estimate/scale};
  257. }
  258. I1 = I0;
  259. ++ii;
  260. }
  261. starting_level_ = static_cast<long>(big_nodes_.size() - 2);
  262. return {I1/omega, relative_error_estimate};
  263. }
  264. private:
  265. template<class PreciseReal>
  266. void add_level(size_t i) {
  267. using std::abs;
  268. size_t current_num_levels = big_nodes_.size();
  269. Real unit_roundoff = std::numeric_limits<Real>::epsilon()/2;
  270. // h0 = 1. Then all further levels have h_i = 1/2^i.
  271. // Since the nodes don't nest, we could conceivably divide h by (say) 1.5, or 3.
  272. // It's not clear how much benefit (or loss) would be obtained from this.
  273. PreciseReal h = PreciseReal(1)/PreciseReal(1<<i);
  274. std::vector<Real> bnode_row;
  275. std::vector<Real> bweight_row;
  276. // This is a pretty good estimate for how many elements will be placed in the vector:
  277. bnode_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  278. bweight_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  279. std::vector<Real> lnode_row;
  280. std::vector<Real> lweight_row;
  281. lnode_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  282. lweight_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  283. Real max_weight = 1;
  284. auto alpha = calculate_ooura_alpha(h);
  285. long n = 0;
  286. Real w;
  287. do {
  288. auto precise_nw = ooura_sin_node_and_weight(n, h, alpha);
  289. Real node = static_cast<Real>(precise_nw.first);
  290. Real weight = static_cast<Real>(precise_nw.second);
  291. w = weight;
  292. if (bnode_row.size() == bnode_row.capacity()) {
  293. bnode_row.reserve(2*bnode_row.size());
  294. bweight_row.reserve(2*bnode_row.size());
  295. }
  296. bnode_row.push_back(node);
  297. bweight_row.push_back(weight);
  298. if (abs(weight) > max_weight) {
  299. max_weight = abs(weight);
  300. }
  301. ++n;
  302. // f(t)->0 as t->infty, which is why the weights are computed up to the unit roundoff.
  303. } while(abs(w) > unit_roundoff*max_weight);
  304. // This class tends to consume a lot of memory; shrink the vectors back down to size:
  305. bnode_row.shrink_to_fit();
  306. bweight_row.shrink_to_fit();
  307. // Why we are splitting the nodes into regimes where t_n >> 1 and t_n << 1?
  308. // It will create the opportunity to sensibly truncate the quadrature sum to significant terms.
  309. n = -1;
  310. do {
  311. auto precise_nw = ooura_sin_node_and_weight(n, h, alpha);
  312. Real node = static_cast<Real>(precise_nw.first);
  313. if (node <= 0) {
  314. break;
  315. }
  316. Real weight = static_cast<Real>(precise_nw.second);
  317. w = weight;
  318. using std::isnan;
  319. if (isnan(node)) {
  320. // This occurs at n = -11 in quad precision:
  321. break;
  322. }
  323. if (lnode_row.size() > 0) {
  324. if (lnode_row[lnode_row.size()-1] == node) {
  325. // The nodes have fused into each other:
  326. break;
  327. }
  328. }
  329. if (lnode_row.size() == lnode_row.capacity()) {
  330. lnode_row.reserve(2*lnode_row.size());
  331. lweight_row.reserve(2*lnode_row.size());
  332. }
  333. lnode_row.push_back(node);
  334. lweight_row.push_back(weight);
  335. if (abs(weight) > max_weight) {
  336. max_weight = abs(weight);
  337. }
  338. --n;
  339. // f(t)->infty is possible as t->0, hence compute up to the min.
  340. } while(abs(w) > (std::numeric_limits<Real>::min)()*max_weight);
  341. lnode_row.shrink_to_fit();
  342. lweight_row.shrink_to_fit();
  343. #ifdef BOOST_MATH_HAS_THREADS
  344. // std::scoped_lock once C++17 is more common?
  345. std::lock_guard<std::mutex> lock(node_weight_mutex_);
  346. #endif
  347. // Another thread might have already finished this calculation and appended it to the nodes/weights:
  348. if (current_num_levels == big_nodes_.size()) {
  349. big_nodes_.push_back(bnode_row);
  350. bweights_.push_back(bweight_row);
  351. little_nodes_.push_back(lnode_row);
  352. lweights_.push_back(lweight_row);
  353. }
  354. }
  355. template<class F>
  356. Real estimate_integral(F const & f, Real omega, size_t i) {
  357. // Because so few function evaluations are required to get high accuracy on the integrals in the tests,
  358. // Kahan summation doesn't really help.
  359. //auto cond = boost::math::tools::summation_condition_number<Real, true>(0);
  360. Real I0 = 0;
  361. auto const & b_nodes = big_nodes_[i];
  362. auto const & b_weights = bweights_[i];
  363. // Will benchmark if this is helpful:
  364. Real inv_omega = 1/omega;
  365. for(size_t j = 0 ; j < b_nodes.size(); ++j) {
  366. I0 += f(b_nodes[j]*inv_omega)*b_weights[j];
  367. }
  368. auto const & l_nodes = little_nodes_[i];
  369. auto const & l_weights = lweights_[i];
  370. // If f decays rapidly as |t|->infty, not all of these calls are necessary.
  371. for (size_t j = 0; j < l_nodes.size(); ++j) {
  372. I0 += f(l_nodes[j]*inv_omega)*l_weights[j];
  373. }
  374. return I0;
  375. }
  376. #ifdef BOOST_MATH_HAS_THREADS
  377. std::mutex node_weight_mutex_;
  378. #endif
  379. // Nodes for n >= 0, giving t_n = pi*phi(nh)/h. Generally t_n >> 1.
  380. std::vector<std::vector<Real>> big_nodes_;
  381. // The term bweights_ will indicate that these are weights corresponding
  382. // to the big nodes:
  383. std::vector<std::vector<Real>> bweights_;
  384. // Nodes for n < 0: Generally t_n << 1, and an invariant is that t_n > 0.
  385. std::vector<std::vector<Real>> little_nodes_;
  386. std::vector<std::vector<Real>> lweights_;
  387. Real rel_err_goal_;
  388. #ifdef BOOST_MATH_HAS_THREADS
  389. std::atomic<long> starting_level_{};
  390. #else
  391. long starting_level_;
  392. #endif
  393. size_t requested_levels_;
  394. };
  395. template<class Real>
  396. class ooura_fourier_cos_detail {
  397. public:
  398. ooura_fourier_cos_detail(const Real relative_error_goal, size_t levels) {
  399. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  400. std::cout << "ooura_fourier_cos with relative error goal " << relative_error_goal
  401. << " & " << levels << " levels." << std::endl;
  402. std::cout << "epsilon for type = " << std::numeric_limits<Real>::epsilon() << std::endl;
  403. #endif // BOOST_MATH_INSTRUMENT_OOURA
  404. if (relative_error_goal < std::numeric_limits<Real>::epsilon() * 2) {
  405. throw std::domain_error("The relative error goal cannot be smaller than the unit roundoff!");
  406. }
  407. using std::abs;
  408. requested_levels_ = levels;
  409. starting_level_ = 0;
  410. rel_err_goal_ = relative_error_goal;
  411. big_nodes_.reserve(levels);
  412. bweights_.reserve(levels);
  413. little_nodes_.reserve(levels);
  414. lweights_.reserve(levels);
  415. for (size_t i = 0; i < levels; ++i) {
  416. if (std::is_same<Real, float>::value) {
  417. add_level<double>(i);
  418. }
  419. else if (std::is_same<Real, double>::value) {
  420. add_level<long double>(i);
  421. }
  422. else {
  423. add_level<Real>(i);
  424. }
  425. }
  426. }
  427. template<class F>
  428. std::pair<Real,Real> integrate(F const & f, Real omega) {
  429. using std::abs;
  430. using std::max;
  431. using boost::math::constants::pi;
  432. if (omega == 0) {
  433. throw std::domain_error("At omega = 0, the integral is not oscillatory. The user must choose an appropriate method for this case.\n");
  434. }
  435. if (omega < 0) {
  436. return this->integrate(f, -omega);
  437. }
  438. Real I1 = std::numeric_limits<Real>::quiet_NaN();
  439. Real absolute_error_estimate = std::numeric_limits<Real>::quiet_NaN();
  440. Real scale = std::numeric_limits<Real>::quiet_NaN();
  441. size_t i = starting_level_;
  442. do {
  443. Real I0 = estimate_integral(f, omega, i);
  444. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  445. print_ooura_estimate(i, I0, I1, omega);
  446. #endif
  447. absolute_error_estimate = abs(I0-I1);
  448. scale = (max)(abs(I0), abs(I1));
  449. if (!isnan(I1) && absolute_error_estimate <= rel_err_goal_*scale) {
  450. starting_level_ = (max)(long(i) - 1, long(0));
  451. return {I0/omega, absolute_error_estimate/scale};
  452. }
  453. I1 = I0;
  454. } while(++i < big_nodes_.size());
  455. size_t max_additional_levels = 4;
  456. while (big_nodes_.size() < requested_levels_ + max_additional_levels) {
  457. size_t ii = big_nodes_.size();
  458. if (std::is_same<Real, float>::value) {
  459. add_level<double>(ii);
  460. }
  461. else if (std::is_same<Real, double>::value) {
  462. add_level<long double>(ii);
  463. }
  464. else {
  465. add_level<Real>(ii);
  466. }
  467. Real I0 = estimate_integral(f, omega, ii);
  468. #ifdef BOOST_MATH_INSTRUMENT_OOURA
  469. print_ooura_estimate(ii, I0, I1, omega);
  470. #endif
  471. absolute_error_estimate = abs(I0-I1);
  472. scale = (max)(abs(I0), abs(I1));
  473. if (absolute_error_estimate <= rel_err_goal_*scale) {
  474. starting_level_ = (max)(long(ii) - 1, long(0));
  475. return {I0/omega, absolute_error_estimate/scale};
  476. }
  477. I1 = I0;
  478. ++ii;
  479. }
  480. starting_level_ = static_cast<long>(big_nodes_.size() - 2);
  481. return {I1/omega, absolute_error_estimate/scale};
  482. }
  483. private:
  484. template<class PreciseReal>
  485. void add_level(size_t i) {
  486. using std::abs;
  487. size_t current_num_levels = big_nodes_.size();
  488. Real unit_roundoff = std::numeric_limits<Real>::epsilon()/2;
  489. PreciseReal h = PreciseReal(1)/PreciseReal(1<<i);
  490. std::vector<Real> bnode_row;
  491. std::vector<Real> bweight_row;
  492. bnode_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  493. bweight_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  494. std::vector<Real> lnode_row;
  495. std::vector<Real> lweight_row;
  496. lnode_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  497. lweight_row.reserve((static_cast<size_t>(1)<<i)*sizeof(Real));
  498. Real max_weight = 1;
  499. auto alpha = calculate_ooura_alpha(h);
  500. long n = 0;
  501. Real w;
  502. do {
  503. auto precise_nw = ooura_cos_node_and_weight(n, h, alpha);
  504. Real node = static_cast<Real>(precise_nw.first);
  505. Real weight = static_cast<Real>(precise_nw.second);
  506. w = weight;
  507. if (bnode_row.size() == bnode_row.capacity()) {
  508. bnode_row.reserve(2*bnode_row.size());
  509. bweight_row.reserve(2*bnode_row.size());
  510. }
  511. bnode_row.push_back(node);
  512. bweight_row.push_back(weight);
  513. if (abs(weight) > max_weight) {
  514. max_weight = abs(weight);
  515. }
  516. ++n;
  517. // f(t)->0 as t->infty, which is why the weights are computed up to the unit roundoff.
  518. } while(abs(w) > unit_roundoff*max_weight);
  519. bnode_row.shrink_to_fit();
  520. bweight_row.shrink_to_fit();
  521. n = -1;
  522. do {
  523. auto precise_nw = ooura_cos_node_and_weight(n, h, alpha);
  524. Real node = static_cast<Real>(precise_nw.first);
  525. // The function cannot be singular at zero,
  526. // so zero is not a unreasonable node,
  527. // unlike in the case of the Fourier Sine.
  528. // Hence only break if the node is negative.
  529. if (node < 0) {
  530. break;
  531. }
  532. Real weight = static_cast<Real>(precise_nw.second);
  533. w = weight;
  534. if (lnode_row.size() > 0) {
  535. if (lnode_row.back() == node) {
  536. // The nodes have fused into each other:
  537. break;
  538. }
  539. }
  540. if (lnode_row.size() == lnode_row.capacity()) {
  541. lnode_row.reserve(2*lnode_row.size());
  542. lweight_row.reserve(2*lnode_row.size());
  543. }
  544. lnode_row.push_back(node);
  545. lweight_row.push_back(weight);
  546. if (abs(weight) > max_weight) {
  547. max_weight = abs(weight);
  548. }
  549. --n;
  550. } while(abs(w) > (std::numeric_limits<Real>::min)()*max_weight);
  551. lnode_row.shrink_to_fit();
  552. lweight_row.shrink_to_fit();
  553. #ifdef BOOST_MATH_HAS_THREADS
  554. std::lock_guard<std::mutex> lock(node_weight_mutex_);
  555. #endif
  556. // Another thread might have already finished this calculation and appended it to the nodes/weights:
  557. if (current_num_levels == big_nodes_.size()) {
  558. big_nodes_.push_back(bnode_row);
  559. bweights_.push_back(bweight_row);
  560. little_nodes_.push_back(lnode_row);
  561. lweights_.push_back(lweight_row);
  562. }
  563. }
  564. template<class F>
  565. Real estimate_integral(F const & f, Real omega, size_t i) {
  566. Real I0 = 0;
  567. auto const & b_nodes = big_nodes_[i];
  568. auto const & b_weights = bweights_[i];
  569. Real inv_omega = 1/omega;
  570. for(size_t j = 0 ; j < b_nodes.size(); ++j) {
  571. I0 += f(b_nodes[j]*inv_omega)*b_weights[j];
  572. }
  573. auto const & l_nodes = little_nodes_[i];
  574. auto const & l_weights = lweights_[i];
  575. for (size_t j = 0; j < l_nodes.size(); ++j) {
  576. I0 += f(l_nodes[j]*inv_omega)*l_weights[j];
  577. }
  578. return I0;
  579. }
  580. #ifdef BOOST_MATH_HAS_THREADS
  581. std::mutex node_weight_mutex_;
  582. #endif
  583. std::vector<std::vector<Real>> big_nodes_;
  584. std::vector<std::vector<Real>> bweights_;
  585. std::vector<std::vector<Real>> little_nodes_;
  586. std::vector<std::vector<Real>> lweights_;
  587. Real rel_err_goal_;
  588. #ifdef BOOST_MATH_HAS_THREADS
  589. std::atomic<long> starting_level_{};
  590. #else
  591. long starting_level_;
  592. #endif
  593. size_t requested_levels_;
  594. };
  595. }}}}
  596. #endif