• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 ///////////////////////////////////////////////////////////////////////////////
2 //      Copyright Christopher Kormanyos 2012 - 2015, 2020.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 //
7 
8 // This example uses Boost.Multiprecision to implement
9 // a high-precision Gauss-Laguerre quadrature integration.
10 // The quadrature is used to calculate the airy_ai(x) function
11 // for real-valued x on the positive axis with x.ge.1.
12 
13 // In this way, the integral representation could be seen
14 // as part of a scheme to calculate real-valued Airy functions
15 // on the positive axis for medium to large argument.
16 // A Taylor series or hypergeometric function (not part
17 // of this example) could be used for smaller arguments.
18 
19 // This example has been tested with decimal digits counts
20 // ranging from 21...301, by adjusting the parameter
21 // local::my_digits10 at compile time.
22 
23 // The quadrature integral representaion of airy_ai(x) used
24 // in this example can be found in:
25 
26 // A. Gil, J. Segura, N.M. Temme, "Numerical Methods for Special
27 // Functions" (SIAM Press 2007), Sect. 5.3.3, in particular Eq. 5.110,
28 // page 145. Subsequently, Gil et al's book cites the another work:
29 // W. Gautschi, "Computation of Bessel and Airy functions and of
30 // related Gaussian quadrature formulae", BIT, 42 (2002), pp. 110-118.
31 
32 #include <cmath>
33 #include <cstdint>
34 #include <functional>
35 #include <iomanip>
36 #include <iostream>
37 #include <numeric>
38 #include <sstream>
39 #include <tuple>
40 #include <vector>
41 
42 #include <boost/cstdfloat.hpp>
43 #include <boost/math/constants/constants.hpp>
44 #include <boost/math/special_functions/cbrt.hpp>
45 #include <boost/math/special_functions/bessel.hpp>
46 #include <boost/math/special_functions/factorials.hpp>
47 #include <boost/math/special_functions/gamma.hpp>
48 #include <boost/math/tools/roots.hpp>
49 #include <boost/multiprecision/cpp_dec_float.hpp>
50 #include <boost/noncopyable.hpp>
51 
52 namespace gauss { namespace laguerre {
53 
54 namespace util {
55 
56 void progress_bar(std::ostream& os, const float percent);
57 
progress_bar(std::ostream & os,const float percent)58 void progress_bar(std::ostream& os, const float percent)
59 {
60   std::stringstream strstrm;
61 
62   strstrm.precision(1);
63 
64   strstrm << std::fixed << percent << "%";
65 
66   os << strstrm.str() << "\r";
67 
68   os.flush();
69 }
70 
71 }
72 
73 namespace detail
74 {
75   template<typename T>
76   class laguerre_l_object BOOST_FINAL
77   {
78   public:
laguerre_l_object(const int n,const T a)79     laguerre_l_object(const int n, const T a) BOOST_NOEXCEPT
80       : order(n),
81         alpha(a),
82         p1   (0),
83         d2   (0) { }
84 
operator =(const laguerre_l_object & other)85     laguerre_l_object& operator=(const laguerre_l_object& other)
86     {
87       if(this != other)
88       {
89         order = other.order;
90         alpha = other.alpha;
91         p1    = other.p1;
92         d2    = other.d2;
93       }
94 
95       return *this;
96     }
97 
operator ()(const T & x) const98     T operator()(const T& x) const BOOST_NOEXCEPT
99     {
100       // Calculate (via forward recursion):
101       // * the value of the Laguerre function L(n, alpha, x), called (p2),
102       // * the value of the derivative of the Laguerre function (d2),
103       // * and the value of the corresponding Laguerre function of
104       //   previous order (p1).
105 
106       // Return the value of the function (p2) in order to be used as a
107       // function object with Boost.Math root-finding. Store the values
108       // of the Laguerre function derivative (d2) and the Laguerre function
109       // of previous order (p1) in class members for later use.
110 
111         p1 = T(0);
112       T p2 = T(1);
113         d2 = T(0);
114 
115       T j_plus_alpha = alpha;
116       T two_j_plus_one_plus_alpha_minus_x = (1 + alpha) - x;
117 
118       const T my_two = 2;
119 
120       for(int j = 0; j < order; ++j)
121       {
122         const T p0(p1);
123 
124         // Set the value of the previous Laguerre function.
125         p1 = p2;
126 
127         // Use a recurrence relation to compute the value of the Laguerre function.
128         p2 = ((two_j_plus_one_plus_alpha_minus_x * p1) - (j_plus_alpha * p0)) / (j + 1);
129 
130         ++j_plus_alpha;
131         two_j_plus_one_plus_alpha_minus_x += my_two;
132       }
133 
134       // Set the value of the derivative of the Laguerre function.
135       d2 = ((p2 * order) - (j_plus_alpha * p1)) / x;
136 
137       // Return the value of the Laguerre function.
138       return p2;
139     }
140 
previous() const141     const T previous  () const BOOST_NOEXCEPT { return p1; }
derivative() const142     const T derivative() const BOOST_NOEXCEPT { return d2; }
143 
root_tolerance(const T & a,const T & b)144     static bool root_tolerance(const T& a, const T& b) BOOST_NOEXCEPT
145     {
146       using std::fabs;
147 
148       // The relative tolerance here is: ((a - b) * 2) / (a + b).
149       return ((fabs(a - b) * 2) < (fabs(a + b) * std::numeric_limits<T>::epsilon()));
150     }
151 
152   private:
153     const   int order;
154     const   T   alpha;
155     mutable T   p1;
156     mutable T   d2;
157   };
158 
159   template<typename T>
160   class abscissas_and_weights : private boost::noncopyable
161   {
162   public:
abscissas_and_weights(const int n,const T a)163     abscissas_and_weights(const int n, const T a) : order(n),
164                                                     alpha(a),
165                                                     xi   (),
166                                                     wi   ()
167     {
168       if(alpha < -20.0F)
169       {
170         // Users can decide to perform different range checking.
171         std::cout << "Range error: the order of the Laguerre function must exceed -20.0."
172                   << std::endl;
173       }
174       else
175       {
176         calculate();
177       }
178     }
179 
abscissa_n() const180     const std::vector<T>& abscissa_n() const BOOST_NOEXCEPT { return xi; }
weight_n() const181     const std::vector<T>& weight_n  () const BOOST_NOEXCEPT { return wi; }
182 
183   private:
184     const int order;
185     const T   alpha;
186 
187     std::vector<T> xi;
188     std::vector<T> wi;
189 
abscissas_and_weights()190     abscissas_and_weights() : order(),
191                               alpha(),
192                               xi   (),
193                               wi   () { }
194 
calculate()195     void calculate()
196     {
197       using std::abs;
198 
199       std::cout << "Finding the approximate roots..." << std::endl;
200 
201       std::vector<std::tuple<T, T>> root_estimates;
202 
203       root_estimates.reserve(static_cast<typename std::vector<std::tuple<T, T>>::size_type>(order));
204 
205       const laguerre_l_object<T> laguerre_root_object(order, alpha);
206 
207       // Set the initial values of the step size and the running step
208       // to be used for finding the estimate of the first root.
209       T step_size  = 0.01F;
210       T step       = step_size;
211 
212       T first_laguerre_root = 0.0F;
213 
214       if(alpha < -1.0F)
215       {
216         // Iteratively step through the Laguerre function using a
217         // small step-size in order to find a rough estimate of
218         // the first zero.
219 
220         const bool this_laguerre_value_is_negative = (laguerre_root_object(T(0)) < 0);
221 
222         BOOST_CONSTEXPR_OR_CONST int j_max = 10000;
223 
224         int j = 0;
225 
226         while((j < j_max) && (this_laguerre_value_is_negative != (laguerre_root_object(step) < 0)))
227         {
228           // Increment the step size until the sign of the Laguerre function
229           // switches. This indicates a zero-crossing, signalling the next root.
230           step += step_size;
231 
232           ++j;
233         }
234       }
235       else
236       {
237         // Calculate an estimate of the 1st root of a generalized Laguerre
238         // function using either a Taylor series or an expansion in Bessel
239         // function zeros. The Bessel function zeros expansion is from Tricomi.
240 
241         // Here, we obtain an estimate of the first zero of cyl_bessel_j(alpha, x).
242 
243         T j_alpha_m1;
244 
245         if(alpha < 1.4F)
246         {
247           // For small alpha, use a short series obtained from Mathematica(R).
248           // Series[BesselJZero[v, 1], {v, 0, 3}]
249           // N[%, 12]
250           j_alpha_m1 = (((          T(0.09748661784476F)
251                           * alpha - T(0.17549359276115F))
252                           * alpha + T(1.54288974259931F))
253                           * alpha + T(2.40482555769577F));
254         }
255         else
256         {
257           // For larger alpha, use the first line of Eqs. 10.21.40 in the NIST Handbook.
258           const T alpha_pow_third(boost::math::cbrt(alpha));
259           const T alpha_pow_minus_two_thirds(T(1) / (alpha_pow_third * alpha_pow_third));
260 
261           j_alpha_m1 = alpha * (((((                             + T(0.043F)
262                                     * alpha_pow_minus_two_thirds - T(0.0908F))
263                                     * alpha_pow_minus_two_thirds - T(0.00397F))
264                                     * alpha_pow_minus_two_thirds + T(1.033150F))
265                                     * alpha_pow_minus_two_thirds + T(1.8557571F))
266                                     * alpha_pow_minus_two_thirds + T(1.0F));
267         }
268 
269         const T vf             = (  T(order * 4U)
270                                   + T(alpha * 2U)
271                                   + T(2U));
272         const T vf2            = vf * vf;
273         const T j_alpha_m1_sqr = j_alpha_m1 * j_alpha_m1;
274 
275         first_laguerre_root = (j_alpha_m1_sqr * (   -T(0.6666666666667F)
276                                                  + ((T(0.6666666666667F) * alpha) * alpha)
277                                                  +  (T(0.3333333333333F) * j_alpha_m1_sqr) + vf2)) / (vf2 * vf);
278       }
279 
280       bool this_laguerre_value_is_negative = (laguerre_root_object(T(0)) < 0);
281 
282       // Re-set the initial value of the step-size based on the
283       // estimate of the first root.
284       step_size = first_laguerre_root / 2;
285       step      = step_size;
286 
287       // Step through the Laguerre function using a step-size
288       // of dynamic width in order to find the zero crossings
289       // of the Laguerre function, providing rough estimates
290       // of the roots. Refine the brackets with a few bisection
291       // steps, and store the results as bracketed root estimates.
292 
293       while(root_estimates.size() < static_cast<std::size_t>(order))
294       {
295         // Increment the step size until the sign of the Laguerre function
296         // switches. This indicates a zero-crossing, signalling the next root.
297         step += step_size;
298 
299         if(this_laguerre_value_is_negative != (laguerre_root_object(step) < 0))
300         {
301           // We have found the next zero-crossing.
302 
303           // Change the running sign of the Laguerre function.
304           this_laguerre_value_is_negative = (!this_laguerre_value_is_negative);
305 
306           // We have found the first zero-crossing. Put a loose bracket around
307           // the root using a window. Here, we know that the first root lies
308           // between (x - step_size) < root < x.
309 
310           // Before storing the approximate root, perform a couple of
311           // bisection steps in order to tighten up the root bracket.
312           boost::uintmax_t a_couple_of_iterations = 4U;
313 
314           const std::pair<T, T>
315             root_estimate_bracket = boost::math::tools::bisect(laguerre_root_object,
316                                                                step - step_size,
317                                                                step,
318                                                                laguerre_l_object<T>::root_tolerance,
319                                                                a_couple_of_iterations);
320 
321           static_cast<void>(a_couple_of_iterations);
322 
323           // Store the refined root estimate as a bracketed range in a tuple.
324           root_estimates.push_back(std::tuple<T, T>(std::get<0>(root_estimate_bracket),
325                                                     std::get<1>(root_estimate_bracket)));
326 
327           if(    (root_estimates.size() == 1U)
328              || ((root_estimates.size() % 8U) == 0U)
329              ||  (root_estimates.size() == static_cast<std::size_t>(order)))
330           {
331             const float progress = (100.0F * static_cast<float>(root_estimates.size())) / static_cast<float>(order);
332 
333             std::cout << "root_estimates.size(): "
334                       << root_estimates.size()
335                       << ", "
336                       ;
337 
338             util::progress_bar(std::cout, progress);
339           }
340 
341           if(root_estimates.size() >= static_cast<std::size_t>(2U))
342           {
343             // Determine the next step size. This is based on the distance between
344             // the previous two roots, whereby the estimates of the previous roots
345             // are computed by taking the average of the lower and upper range of
346             // the root-estimate bracket.
347 
348             const T r0 = (  std::get<0>(*(root_estimates.crbegin() + 1U))
349                           + std::get<1>(*(root_estimates.crbegin() + 1U))) / 2;
350 
351             const T r1 = (  std::get<0>(*root_estimates.crbegin())
352                           + std::get<1>(*root_estimates.crbegin())) / 2;
353 
354             const T distance_between_previous_roots = r1 - r0;
355 
356             step_size = distance_between_previous_roots / 3;
357           }
358         }
359       }
360 
361       const T norm_g =
362         ((alpha == 0) ? T(-1)
363                       : -boost::math::tgamma(alpha + order) / boost::math::factorial<T>(order - 1));
364 
365       xi.reserve(root_estimates.size());
366       wi.reserve(root_estimates.size());
367 
368       std::cout << std::endl;
369 
370       // Calculate the abscissas and weights to full precision.
371       for(std::size_t i = static_cast<std::size_t>(0U); i < root_estimates.size(); ++i)
372       {
373         if(   ((i % 8U) == 0U)
374            || ( i == root_estimates.size() - 1U))
375         {
376           const float progress = (100.0F * static_cast<float>(i + 1U)) / static_cast<float>(root_estimates.size());
377 
378           std::cout << "Calculating abscissas and weights. Processed "
379                     << (i + 1U)
380                     << ", "
381                     ;
382 
383           util::progress_bar(std::cout, progress);
384         }
385 
386         // Calculate the abscissas using iterative root-finding.
387 
388         // Select the maximum allowed iterations, being at least 20.
389         // The determination of the maximum allowed iterations is
390         // based on the number of decimal digits in the numerical
391         // type T.
392         BOOST_CONSTEXPR_OR_CONST int local_math_tools_digits10 =
393           static_cast<int>(static_cast<boost::float_least32_t>(boost::math::tools::digits<T>()) * BOOST_FLOAT32_C(0.301));
394 
395         const boost::uintmax_t number_of_iterations_allowed = (std::max)(20, local_math_tools_digits10 / 2);
396 
397         boost::uintmax_t number_of_iterations_used = number_of_iterations_allowed;
398 
399         // Perform the root-finding using ACM TOMS 748 from Boost.Math.
400         const std::pair<T, T>
401           laguerre_root_bracket = boost::math::tools::toms748_solve(laguerre_root_object,
402                                                                     std::get<0>(root_estimates.at(i)),
403                                                                     std::get<1>(root_estimates.at(i)),
404                                                                     laguerre_l_object<T>::root_tolerance,
405                                                                     number_of_iterations_used);
406 
407         static_cast<void>(number_of_iterations_used);
408 
409         // Compute the Laguerre root as the average of the values from
410         // the solved root bracket.
411         const T laguerre_root = (  std::get<0>(laguerre_root_bracket)
412                                  + std::get<1>(laguerre_root_bracket)) / 2;
413 
414         // Calculate the weight for this Laguerre root. Here, we calculate
415         // the derivative of the Laguerre function and the value of the
416         // previous Laguerre function on the x-axis at the value of this
417         // Laguerre root.
418         static_cast<void>(laguerre_root_object(laguerre_root));
419 
420         // Store the abscissa and weight for this index.
421         xi.push_back(laguerre_root);
422         wi.push_back(norm_g / ((laguerre_root_object.derivative() * order) * laguerre_root_object.previous()));
423       }
424 
425       std::cout << std::endl;
426     }
427   };
428 
429   template<typename T>
430   struct airy_ai_object BOOST_FINAL
431   {
432   public:
airy_ai_objectgauss::laguerre::detail::BOOST_FINAL433     airy_ai_object(const T& x)
434       : my_x     (x),
435         my_zeta  (((sqrt(x) * x) * 2) / 3),
436         my_factor(make_factor(my_zeta)) { }
437 
operator ()gauss::laguerre::detail::BOOST_FINAL438     T operator()(const T& t) const
439     {
440       using std::sqrt;
441 
442       return my_factor / sqrt(boost::math::cbrt(2 + (t / my_zeta)));
443     }
444 
445   private:
446     const T my_x;
447     const T my_zeta;
448     const T my_factor;
449 
airy_ai_objectgauss::laguerre::detail::BOOST_FINAL450     airy_ai_object() : my_x     (),
451                        my_zeta  (),
452                        my_factor() { }
453 
make_factorgauss::laguerre::detail::BOOST_FINAL454     static T make_factor(const T& z)
455     {
456       using std::exp;
457       using std::sqrt;
458 
459       return 1 / ((sqrt(boost::math::constants::pi<T>()) * sqrt(boost::math::cbrt(z * 48))) * (exp(z) * boost::math::tgamma(T(5) / 6)));
460     }
461   };
462 } // namespace detail
463 
464 } } // namespace gauss::laguerre
465 
466 
467 // A float_type is created to handle the desired number of decimal digits from `cpp_dec_float` without using __expression_templates.
468 struct local
469 {
470   BOOST_STATIC_CONSTEXPR unsigned int my_digits10 = 101U;
471 
472   typedef boost::multiprecision::number<boost::multiprecision::cpp_dec_float<my_digits10>,
473                                         boost::multiprecision::et_off>
474   float_type;
475 };
476 
477 BOOST_STATIC_ASSERT_MSG(local::my_digits10 > 20U,
478                         "Error: This example is intended to have more than 20 decimal digits");
479 
main()480 int main()
481 {
482   // Use Gauss-Laguerre quadrature integration to compute airy_ai(x / 7)
483   // with 7 <= x <= 120 and where x is incremented in steps of 1.
484 
485   // During development of this example, we have empirically found
486   // the numbers of Gauss-Laguerre coefficients needed for convergence
487   // when using various counts of base-10 digits.
488 
489   // Let's calibrate, for instance, the number of coefficients needed
490   // at the point x = 1.
491 
492   // Empirical data lead to:
493   // Fit[{{21.0, 3.5}, {51.0, 11.1}, {101.0, 22.5}, {201.0, 46.8}}, {1, d, d^2}, d]
494   // FullSimplify[%]
495   // -1.28301 + (0.235487 + 0.0000178915 d) d
496 
497   // We need significantly more coefficients at smaller real values than are needed
498   // at larger real values because the slope derivative of airy_ai(x) gets more
499   // steep as x approaches zero.
500 
501   // This Gauss-Laguerre quadrature is designed for airy_ai(x) with real-valued x >= 1.
502 
503   BOOST_CONSTEXPR_OR_CONST boost::float_least32_t d = static_cast<boost::float_least32_t>(std::numeric_limits<local::float_type>::digits10);
504 
505   BOOST_CONSTEXPR_OR_CONST boost::float_least32_t laguerre_order_factor = -1.28301F + ((0.235487F + (0.0000178915F * d)) * d);
506 
507   BOOST_CONSTEXPR_OR_CONST int laguerre_order = static_cast<int>(laguerre_order_factor * d);
508 
509   std::cout << "std::numeric_limits<local::float_type>::digits10: " << std::numeric_limits<local::float_type>::digits10 << std::endl;
510   std::cout << "laguerre_order: " << laguerre_order << std::endl;
511 
512   typedef gauss::laguerre::detail::abscissas_and_weights<local::float_type> abscissas_and_weights_type;
513 
514   const abscissas_and_weights_type the_abscissas_and_weights(laguerre_order, local::float_type(-1) / 6);
515 
516   bool result_is_ok = true;
517 
518   for(std::uint32_t u = 7U; u < 121U; ++u)
519   {
520     const local::float_type x = local::float_type(u) / 7;
521 
522     typedef gauss::laguerre::detail::airy_ai_object<local::float_type> airy_ai_object_type;
523 
524     const airy_ai_object_type the_airy_ai_object(x);
525 
526     const local::float_type airy_ai_value =
527       std::inner_product(the_abscissas_and_weights.abscissa_n().cbegin(),
528                          the_abscissas_and_weights.abscissa_n().cend(),
529                          the_abscissas_and_weights.weight_n().cbegin(),
530                          local::float_type(0U),
531                          std::plus<local::float_type>(),
532                          [&the_airy_ai_object](const local::float_type& this_abscissa,
533                                                const local::float_type& this_weight) -> local::float_type
534                          {
535                            return the_airy_ai_object(this_abscissa) * this_weight;
536                          });
537 
538     static const local::float_type one_third = 1.0F / local::float_type(3U);
539 
540     static const local::float_type one_over_pi_times_one_over_sqrt_three =
541       sqrt(one_third) * boost::math::constants::one_div_pi<local::float_type>();
542 
543     const local::float_type sqrt_x = sqrt(x);
544 
545     const local::float_type airy_ai_control =
546        (sqrt_x * one_over_pi_times_one_over_sqrt_three)
547       * boost::math::cyl_bessel_k(one_third, ((2.0F * x) * sqrt_x) * one_third);
548 
549     std::cout << std::setprecision(std::numeric_limits<local::float_type>::digits10)
550               << "airy_ai_value  : "
551               << airy_ai_value
552               << std::endl;
553 
554     std::cout << std::setprecision(std::numeric_limits<local::float_type>::digits10)
555               << "airy_ai_control: "
556               << airy_ai_control
557               << std::endl;
558 
559     const local::float_type delta = fabs(1.0F - (airy_ai_control / airy_ai_value));
560 
561     static const local::float_type tol("1E-" + boost::lexical_cast<std::string>(std::numeric_limits<local::float_type>::digits10 - 7U));
562 
563     result_is_ok &= (delta < tol);
564   }
565 
566   std::cout << std::endl
567             << "Total... result_is_ok: "
568             << std::boolalpha
569             << result_is_ok
570             << std::endl;
571 } // int main()
572 
573 /*
574 
575 
576 Partial output:
577 
578 //[gauss_laguerre_quadrature_output_1
579 
580 std::numeric_limits<local::float_type>::digits10: 101
581 laguerre_order: 2291
582 
583 Finding the approximate roots...
584 root_estimates.size(): 1, 0.0%
585 root_estimates.size(): 8, 0.3%
586 root_estimates.size(): 16, 0.7%
587 ...
588 root_estimates.size(): 2288, 99.9%
589 root_estimates.size(): 2291, 100.0%
590 
591 
592 Calculating abscissas and weights. Processed 1, 0.0%
593 Calculating abscissas and weights. Processed 9, 0.4%
594 ...
595 Calculating abscissas and weights. Processed 2289, 99.9%
596 Calculating abscissas and weights. Processed 2291, 100.0%
597 //] [/gauss_laguerre_quadrature_output_1]
598 
599 //[gauss_laguerre_quadrature_output_2
600 
601 airy_ai_value  : 0.13529241631288141552414742351546630617494414298833070600910205475763353480226572366348710990874867334
602 airy_ai_control: 0.13529241631288141552414742351546630617494414298833070600910205475763353480226572366348710990874868323
603 airy_ai_value  : 0.11392302126009621102904231059693500086750049240884734708541630001378825889924647699516200868335286103
604 airy_ai_control: 0.1139230212600962110290423105969350008675004924088473470854163000137882588992464769951620086833528582
605 ...
606 airy_ai_value  : 3.8990420982303275013276114626640705170145070824317976771461533035231088620152288641360519429331427451e-22
607 airy_ai_control: 3.8990420982303275013276114626640705170145070824317976771461533035231088620152288641360519429331426481e-22
608 
609 Total... result_is_ok: true
610 
611 //] [/gauss_laguerre_quadrature_output_2]
612 
613 
614 */
615