gauss_laguerre_quadrature.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. // Copyright Nick Thompson, 2017
  2. // Copyright John Maddock 2017
  3. // Use, modification and distribution are subject to the
  4. // Boost Software License, Version 1.0.
  5. // (See accompanying file LICENSE_1_0.txt
  6. // or copy at http://www.boost.org/LICENSE_1_0.txt)
  7. #include <cmath>
  8. #include <cstdint>
  9. #include <functional>
  10. #include <iomanip>
  11. #include <iostream>
  12. #include <numeric>
  13. #include <boost/math/constants/constants.hpp>
  14. #include <boost/math/special_functions/cbrt.hpp>
  15. #include <boost/math/special_functions/factorials.hpp>
  16. #include <boost/math/special_functions/gamma.hpp>
  17. #include <boost/math/tools/roots.hpp>
  18. #include <boost/noncopyable.hpp>
  19. #define CPP_BIN_FLOAT 1
  20. #define CPP_DEC_FLOAT 2
  21. #define CPP_MPFR_FLOAT 3
  22. //#define MP_TYPE CPP_BIN_FLOAT
  23. #define MP_TYPE CPP_DEC_FLOAT
  24. //#define MP_TYPE CPP_MPFR_FLOAT
  25. namespace
  26. {
  27. struct digits_characteristics
  28. {
  29. static const int digits10 = 300;
  30. static const int guard_digits = 6;
  31. };
  32. }
  33. #if (MP_TYPE == CPP_BIN_FLOAT)
  34. #include <boost/multiprecision/cpp_bin_float.hpp>
  35. namespace mp = boost::multiprecision;
  36. typedef mp::number<mp::cpp_bin_float<digits_characteristics::digits10 + digits_characteristics::guard_digits>, mp::et_off> mp_type;
  37. #elif (MP_TYPE == CPP_DEC_FLOAT)
  38. #include <boost/multiprecision/cpp_dec_float.hpp>
  39. namespace mp = boost::multiprecision;
  40. typedef mp::number<mp::cpp_dec_float<digits_characteristics::digits10 + digits_characteristics::guard_digits>, mp::et_off> mp_type;
  41. #elif (MP_TYPE == CPP_MPFR_FLOAT)
  42. #include <boost/multiprecision/mpfr.hpp>
  43. namespace mp = boost::multiprecision;
  44. typedef mp::number<mp::mpfr_float_backend<digits_characteristics::digits10 + digits_characteristics::guard_digits>, mp::et_off> mp_type;
  45. #else
  46. #error MP_TYPE is undefined
  47. #endif
  48. template<typename T>
  49. class laguerre_function_object
  50. {
  51. public:
  52. laguerre_function_object(const int n, const T a) : order(n),
  53. alpha(a),
  54. p1 (0),
  55. d2 (0) { }
  56. laguerre_function_object(const laguerre_function_object& other) : order(other.order),
  57. alpha(other.alpha),
  58. p1 (other.p1),
  59. d2 (other.d2) { }
  60. ~laguerre_function_object() { }
  61. T operator()(const T& x) const
  62. {
  63. // Calculate (via forward recursion):
  64. // * the value of the Laguerre function L(n, alpha, x), called (p2),
  65. // * the value of the derivative of the Laguerre function (d2),
  66. // * and the value of the corresponding Laguerre function of
  67. // previous order (p1).
  68. // Return the value of the function (p2) in order to be used as a
  69. // function object with Boost.Math root-finding. Store the values
  70. // of the Laguerre function derivative (d2) and the Laguerre function
  71. // of previous order (p1) in class members for later use.
  72. p1 = T(0);
  73. T p2 = T(1);
  74. d2 = T(0);
  75. T j_plus_alpha(alpha);
  76. T two_j_plus_one_plus_alpha_minus_x(1 + alpha - x);
  77. int j;
  78. const T my_two(2);
  79. for(j = 0; j < order; ++j)
  80. {
  81. const T p0(p1);
  82. // Set the value of the previous Laguerre function.
  83. p1 = p2;
  84. // Use a recurrence relation to compute the value of the Laguerre function.
  85. p2 = ((two_j_plus_one_plus_alpha_minus_x * p1) - (j_plus_alpha * p0)) / (j + 1);
  86. ++j_plus_alpha;
  87. two_j_plus_one_plus_alpha_minus_x += my_two;
  88. }
  89. // Set the value of the derivative of the Laguerre function.
  90. d2 = ((p2 * j) - (j_plus_alpha * p1)) / x;
  91. // Return the value of the Laguerre function.
  92. return p2;
  93. }
  94. const T& previous () const { return p1; }
  95. const T& derivative() const { return d2; }
  96. static bool root_tolerance(const T& a, const T& b)
  97. {
  98. using std::abs;
  99. // The relative tolerance here is: ((a - b) * 2) / (a + b).
  100. return (abs((a - b) * 2) < ((a + b) * boost::math::tools::epsilon<T>()));
  101. }
  102. private:
  103. const int order;
  104. const T alpha;
  105. mutable T p1;
  106. mutable T d2;
  107. laguerre_function_object();
  108. const laguerre_function_object& operator=(const laguerre_function_object&);
  109. };
  110. template<typename T>
  111. class guass_laguerre_abscissas_and_weights : private boost::noncopyable
  112. {
  113. public:
  114. guass_laguerre_abscissas_and_weights(const int n, const T a) : order(n),
  115. alpha(a),
  116. valid(true),
  117. xi (),
  118. wi ()
  119. {
  120. if(alpha < -20.0F)
  121. {
  122. // TBD: If we ever boostify this, throw a range error here.
  123. // If so, then also document it in the docs.
  124. std::cout << "Range error: the order of the Laguerre function must exceed -20.0." << std::endl;
  125. }
  126. else
  127. {
  128. calculate();
  129. }
  130. }
  131. virtual ~guass_laguerre_abscissas_and_weights() { }
  132. const std::vector<T>& abscissas() const { return xi; }
  133. const std::vector<T>& weights () const { return wi; }
  134. bool get_valid() const { return valid; }
  135. private:
  136. const int order;
  137. const T alpha;
  138. bool valid;
  139. std::vector<T> xi;
  140. std::vector<T> wi;
  141. void calculate()
  142. {
  143. using std::abs;
  144. std::cout << "finding approximate roots..." << std::endl;
  145. std::vector<boost::math::tuple<T, T> > root_estimates;
  146. root_estimates.reserve(static_cast<typename std::vector<boost::math::tuple<T, T> >::size_type>(order));
  147. const laguerre_function_object<T> laguerre_object(order, alpha);
  148. // Set the initial values of the step size and the running step
  149. // to be used for finding the estimate of the first root.
  150. T step_size = 0.01F;
  151. T step = step_size;
  152. T first_laguerre_root = 0.0F;
  153. bool first_laguerre_root_has_been_found = true;
  154. if(alpha < -1.0F)
  155. {
  156. // Iteratively step through the Laguerre function using a
  157. // small step-size in order to find a rough estimate of
  158. // the first zero.
  159. bool this_laguerre_value_is_negative = (laguerre_object(mp_type(0)) < 0);
  160. static const int j_max = 10000;
  161. int j;
  162. for(j = 0; (j < j_max) && (this_laguerre_value_is_negative != (laguerre_object(step) < 0)); ++j)
  163. {
  164. // Increment the step size until the sign of the Laguerre function
  165. // switches. This indicates a zero-crossing, signalling the next root.
  166. step += step_size;
  167. }
  168. if(j >= j_max)
  169. {
  170. first_laguerre_root_has_been_found = false;
  171. }
  172. else
  173. {
  174. // We have found the first zero-crossing. Put a loose bracket around
  175. // the root using a window. Here, we know that the first root lies
  176. // between (x - step_size) < root < x.
  177. // Before storing the approximate root, perform a couple of
  178. // bisection steps in order to tighten up the root bracket.
  179. boost::uintmax_t a_couple_of_iterations = 3U;
  180. const std::pair<T, T>
  181. first_laguerre_root = boost::math::tools::bisect(laguerre_object,
  182. step - step_size,
  183. step,
  184. laguerre_function_object<T>::root_tolerance,
  185. a_couple_of_iterations);
  186. static_cast<void>(a_couple_of_iterations);
  187. }
  188. }
  189. else
  190. {
  191. // Calculate an estimate of the 1st root of a generalized Laguerre
  192. // function using either a Taylor series or an expansion in Bessel
  193. // function zeros. The Bessel function zeros expansion is from Tricomi.
  194. // Here, we obtain an estimate of the first zero of J_alpha(x).
  195. T j_alpha_m1;
  196. if(alpha < 1.4F)
  197. {
  198. // For small alpha, use a short series obtained from Mathematica(R).
  199. // Series[BesselJZero[v, 1], {v, 0, 3}]
  200. // N[%, 12]
  201. j_alpha_m1 = ((( 0.09748661784476F
  202. * alpha - 0.17549359276115F)
  203. * alpha + 1.54288974259931F)
  204. * alpha + 2.40482555769577F);
  205. }
  206. else
  207. {
  208. // For larger alpha, use the first line of Eqs. 10.21.40 in the NIST Handbook.
  209. const T alpha_pow_third(boost::math::cbrt(alpha));
  210. const T alpha_pow_minus_two_thirds(T(1) / (alpha_pow_third * alpha_pow_third));
  211. j_alpha_m1 = alpha * ((((( + 0.043F
  212. * alpha_pow_minus_two_thirds - 0.0908F)
  213. * alpha_pow_minus_two_thirds - 0.00397F)
  214. * alpha_pow_minus_two_thirds + 1.033150F)
  215. * alpha_pow_minus_two_thirds + 1.8557571F)
  216. * alpha_pow_minus_two_thirds + 1.0F);
  217. }
  218. const T vf = ((order * 4.0F) + (alpha * 2.0F) + 2.0F);
  219. const T vf2 = vf * vf;
  220. const T j_alpha_m1_sqr = j_alpha_m1 * j_alpha_m1;
  221. first_laguerre_root = (j_alpha_m1_sqr * (-0.6666666666667F + ((0.6666666666667F * alpha) * alpha) + (0.3333333333333F * j_alpha_m1_sqr) + vf2)) / (vf2 * vf);
  222. }
  223. if(first_laguerre_root_has_been_found)
  224. {
  225. bool this_laguerre_value_is_negative = (laguerre_object(mp_type(0)) < 0);
  226. // Re-set the initial value of the step-size based on the
  227. // estimate of the first root.
  228. step_size = first_laguerre_root / 2;
  229. step = step_size;
  230. // Step through the Laguerre function using a step-size
  231. // of dynamic width in order to find the zero crossings
  232. // of the Laguerre function, providing rough estimates
  233. // of the roots. Refine the brackets with a few bisection
  234. // steps, and store the results as bracketed root estimates.
  235. while(static_cast<int>(root_estimates.size()) < order)
  236. {
  237. // Increment the step size until the sign of the Laguerre function
  238. // switches. This indicates a zero-crossing, signalling the next root.
  239. step += step_size;
  240. if(this_laguerre_value_is_negative != (laguerre_object(step) < 0))
  241. {
  242. // We have found the next zero-crossing.
  243. // Change the running sign of the Laguerre function.
  244. this_laguerre_value_is_negative = (!this_laguerre_value_is_negative);
  245. // We have found the first zero-crossing. Put a loose bracket around
  246. // the root using a window. Here, we know that the first root lies
  247. // between (x - step_size) < root < x.
  248. // Before storing the approximate root, perform a couple of
  249. // bisection steps in order to tighten up the root bracket.
  250. boost::uintmax_t a_couple_of_iterations = 3U;
  251. const std::pair<T, T>
  252. root_estimate_bracket = boost::math::tools::bisect(laguerre_object,
  253. step - step_size,
  254. step,
  255. laguerre_function_object<T>::root_tolerance,
  256. a_couple_of_iterations);
  257. static_cast<void>(a_couple_of_iterations);
  258. // Store the refined root estimate as a bracketed range in a tuple.
  259. root_estimates.push_back(boost::math::tuple<T, T>(root_estimate_bracket.first,
  260. root_estimate_bracket.second));
  261. if(root_estimates.size() >= static_cast<std::size_t>(2U))
  262. {
  263. // Determine the next step size. This is based on the distance between
  264. // the previous two roots, whereby the estimates of the previous roots
  265. // are computed by taking the average of the lower and upper range of
  266. // the root-estimate bracket.
  267. const T r0 = ( boost::math::get<0>(*(root_estimates.rbegin() + 1U))
  268. + boost::math::get<1>(*(root_estimates.rbegin() + 1U))) / 2;
  269. const T r1 = ( boost::math::get<0>(*root_estimates.rbegin())
  270. + boost::math::get<1>(*root_estimates.rbegin())) / 2;
  271. const T distance_between_previous_roots = r1 - r0;
  272. step_size = distance_between_previous_roots / 3;
  273. }
  274. }
  275. }
  276. const T norm_g =
  277. ((alpha == 0) ? T(-1)
  278. : -boost::math::tgamma(alpha + order) / boost::math::factorial<T>(order - 1));
  279. xi.reserve(root_estimates.size());
  280. wi.reserve(root_estimates.size());
  281. // Calculate the abscissas and weights to full precision.
  282. for(std::size_t i = static_cast<std::size_t>(0U); i < root_estimates.size(); ++i)
  283. {
  284. std::cout << "calculating abscissa and weight for index: " << i << std::endl;
  285. // Calculate the abscissas using iterative root-finding.
  286. // Select the maximum allowed iterations, being at least 20.
  287. // The determination of the maximum allowed iterations is
  288. // based on the number of decimal digits in the numerical
  289. // type T.
  290. const int my_digits10 = static_cast<int>(static_cast<float>(boost::math::tools::digits<T>()) * 0.301F);
  291. const boost::uintmax_t number_of_iterations_allowed = (std::max)(20, my_digits10 / 2);
  292. boost::uintmax_t number_of_iterations_used = number_of_iterations_allowed;
  293. // Perform the root-finding using ACM TOMS 748 from Boost.Math.
  294. const std::pair<T, T>
  295. laguerre_root_bracket = boost::math::tools::toms748_solve(laguerre_object,
  296. boost::math::get<0>(root_estimates[i]),
  297. boost::math::get<1>(root_estimates[i]),
  298. laguerre_function_object<T>::root_tolerance,
  299. number_of_iterations_used);
  300. // Based on the result of *each* root-finding operation, re-assess
  301. // the validity of the Guass-Laguerre abscissas and weights object.
  302. valid &= (number_of_iterations_used < number_of_iterations_allowed);
  303. // Compute the Laguerre root as the average of the values from
  304. // the solved root bracket.
  305. const T laguerre_root = ( laguerre_root_bracket.first
  306. + laguerre_root_bracket.second) / 2;
  307. // Calculate the weight for this Laguerre root. Here, we calculate
  308. // the derivative of the Laguerre function and the value of the
  309. // previous Laguerre function on the x-axis at the value of this
  310. // Laguerre root.
  311. static_cast<void>(laguerre_object(laguerre_root));
  312. // Store the abscissa and weight for this index.
  313. xi.push_back(laguerre_root);
  314. wi.push_back(norm_g / ((laguerre_object.derivative() * order) * laguerre_object.previous()));
  315. }
  316. }
  317. }
  318. };
  319. namespace
  320. {
  321. template<typename T>
  322. struct gauss_laguerre_ai
  323. {
  324. public:
  325. gauss_laguerre_ai(const T X) : x(X)
  326. {
  327. using std::exp;
  328. using std::sqrt;
  329. zeta = ((sqrt(x) * x) * 2) / 3;
  330. const T zeta_times_48_pow_sixth = sqrt(boost::math::cbrt(zeta * 48));
  331. factor = 1 / ((sqrt(boost::math::constants::pi<T>()) * zeta_times_48_pow_sixth) * (exp(zeta) * gamma_of_five_sixths()));
  332. }
  333. gauss_laguerre_ai(const gauss_laguerre_ai& other) : x (other.x),
  334. zeta (other.zeta),
  335. factor(other.factor) { }
  336. T operator()(const T& t) const
  337. {
  338. using std::sqrt;
  339. return factor / sqrt(boost::math::cbrt(2 + (t / zeta)));
  340. }
  341. private:
  342. const T x;
  343. T zeta;
  344. T factor;
  345. static const T& gamma_of_five_sixths()
  346. {
  347. static const T value = boost::math::tgamma(T(5) / 6);
  348. return value;
  349. }
  350. const gauss_laguerre_ai& operator=(const gauss_laguerre_ai&);
  351. };
  352. template<typename T>
  353. T gauss_laguerre_airy_ai(const T x)
  354. {
  355. static const float digits_factor = static_cast<float>(std::numeric_limits<mp_type>::digits10) / 300.0F;
  356. static const int laguerre_order = static_cast<int>(600.0F * digits_factor);
  357. static const guass_laguerre_abscissas_and_weights<T> abscissas_and_weights(laguerre_order, -T(1) / 6);
  358. T airy_ai_result;
  359. if(abscissas_and_weights.get_valid())
  360. {
  361. const gauss_laguerre_ai<T> this_gauss_laguerre_ai(x);
  362. airy_ai_result =
  363. std::inner_product(abscissas_and_weights.abscissas().begin(),
  364. abscissas_and_weights.abscissas().end(),
  365. abscissas_and_weights.weights().begin(),
  366. T(0),
  367. std::plus<T>(),
  368. [&this_gauss_laguerre_ai](const T& this_abscissa, const T& this_weight) -> T
  369. {
  370. return this_gauss_laguerre_ai(this_abscissa) * this_weight;
  371. });
  372. }
  373. else
  374. {
  375. // TBD: Consider an error message.
  376. airy_ai_result = T(0);
  377. }
  378. return airy_ai_result;
  379. }
  380. }
  381. int main()
  382. {
  383. // Use Gauss-Laguerre integration to compute airy_ai(120 / 7).
  384. // 9 digits
  385. // 3.89904210e-22
  386. // 10 digits
  387. // 3.899042098e-22
  388. // 50 digits.
  389. // 3.8990420982303275013276114626640705170145070824318e-22
  390. // 100 digits.
  391. // 3.899042098230327501327611462664070517014507082431797677146153303523108862015228
  392. // 864136051942933142648e-22
  393. // 200 digits.
  394. // 3.899042098230327501327611462664070517014507082431797677146153303523108862015228
  395. // 86413605194293314264788265460938200890998546786740097437064263800719644346113699
  396. // 77010905030516409847054404055843899790277e-22
  397. // 300 digits.
  398. // 3.899042098230327501327611462664070517014507082431797677146153303523108862015228
  399. // 86413605194293314264788265460938200890998546786740097437064263800719644346113699
  400. // 77010905030516409847054404055843899790277083960877617919088116211775232728792242
  401. // 9346416823281460245814808276654088201413901972239996130752528e-22
  402. // 500 digits.
  403. // 3.899042098230327501327611462664070517014507082431797677146153303523108862015228
  404. // 86413605194293314264788265460938200890998546786740097437064263800719644346113699
  405. // 77010905030516409847054404055843899790277083960877617919088116211775232728792242
  406. // 93464168232814602458148082766540882014139019722399961307525276722937464859521685
  407. // 42826483602153339361960948844649799257455597165900957281659632186012043089610827
  408. // 78871305322190941528281744734605934497977375094921646511687434038062987482900167
  409. // 45127557400365419545e-22
  410. // Mathematica(R) or Wolfram's Alpha:
  411. // N[AiryAi[120 / 7], 300]
  412. std::cout << std::setprecision(digits_characteristics::digits10)
  413. << gauss_laguerre_airy_ai(mp_type(120) / 7)
  414. << std::endl;
  415. }