• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1[/
2Copyright (c) 2017 Nick Thompson
3Use, modification and distribution are subject to the
4Boost Software License, Version 1.0. (See accompanying file
5LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6]
7
8[section:double_exponential Double-exponential quadrature]
9
10[section:de_overview Overview]
11
12[heading Synopsis]
13
14``
15    #include <boost/math/quadrature/tanh_sinh.hpp>
16    #include <boost/math/quadrature/exp_sinh.hpp>
17    #include <boost/math/quadrature/sinh_sinh.hpp>
18
19    namespace boost{ namespace math{
20
21    template<class Real>
22    class tanh_sinh
23    {
24    public:
25        tanh_sinh(size_t max_refinements = 15, const Real& min_complement = tools::min_value<Real>() * 4)
26
27        template<class F>
28        auto integrate(const F f, Real a, Real b,
29                       Real tolerance = tools::root_epsilon<Real>(),
30                       Real* error = nullptr,
31                       Real* L1 = nullptr,
32                       std::size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
33
34        template<class F>
35        auto integrate(const F f, Real
36                       tolerance = tools::root_epsilon<Real>(),
37                       Real* error = nullptr,
38                       Real* L1 = nullptr,
39                       std::size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
40
41    };
42
43    template<class Real>
44    class exp_sinh
45    {
46    public:
47        exp_sinh(size_t max_refinements = 9);
48
49        template<class F>
50        auto integrate(const F f, Real a, Real b,
51                       Real tol = sqrt(std::numeric_limits<Real>::epsilon()),
52                       Real* error = nullptr,
53                       Real* L1 = nullptr,
54                       size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
55        template<class F>
56        auto integrate(const F f,
57                       Real tol = sqrt(std::numeric_limits<Real>::epsilon()),
58                       Real* error = nullptr,
59                       Real* L1 = nullptr,
60                       size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
61    };
62
63    template<class Real>
64    class sinh_sinh
65    {
66    public:
67        sinh_sinh(size_t max_refinements = 9);
68
69        template<class F>
70        auto integrate(const F f,
71                       Real tol = sqrt(std::numeric_limits<Real>::epsilon()),
72                       Real* error = nullptr,
73                       Real* L1 = nullptr,
74                       size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
75    };
76
77}}
78``
79
80These three integration routines provide robust general purpose quadrature, each having a "native" range over which
81quadrature is performed.
82For example, the `sinh_sinh` quadrature integrates over the entire real line, the `tanh_sinh` over (-1, 1),
83and the `exp_sinh` over (0, [infin]).
84The latter integrators also have auxiliary ranges which are handled via a change of variables on the function being integrated,
85so that the `tanh_sinh` can handle integration over /(a, b)/, and `exp_sinh` over /(a, [infin]) and(-[infin], b)/.
86
87Like the other quadrature routines in Boost, these routines support both real and complex-valued integrands.
88
89The `integrate` methods which do not specify a range always integrate over the native range of the method, and generally
90are the most efficient and produce the smallest code, on the other hand the methods which do specify the bounds of integration
91are the most general, and use argument transformations which are generally very robust.  The following table summarizes
92the ranges supported by each method:
93
94[table
95[[Integrator][Native range][Other supported ranges][Comments]]
96[[tanh_sinh]  [(-1,1)]  [(a,b)[br](a,[infin])[br](-[infin],b)[br](-[infin],[infin])]
97      [Special care is taken for endpoints at or near zero to ensure that abscissa values are calculated without the loss of precision
98      that would normally occur.  Likewise when transforming to an infinite endpoint, the additional information which tanh_sinh has
99      internally on abscissa values is used to ensure no loss of precision during the transformation.]]
100[[exp_sinh]  [(0,[infin])]  [(a,[infin])[br](-[infin],0)[br](-[infin],b)]  []]
101[[sinh_sinh]  [(-[infin],[infin])]  [][]]
102]
103
104[endsect] [/section:de_overview Overview]
105
106[section:de_tanh_sinh tanh_sinh]
107
108    template<class Real>
109    class tanh_sinh
110    {
111    public:
112        tanh_sinh(size_t max_refinements = 15, const Real& min_complement = tools::min_value<Real>() * 4)
113
114        template<class F>
115        auto integrate(const F f, Real a, Real b,
116                       Real tolerance = tools::root_epsilon<Real>(),
117                       Real* error = nullptr,
118                       Real* L1 = nullptr,
119                       std::size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
120
121        template<class F>
122        auto integrate(const F f, Real
123                       tolerance = tools::root_epsilon<Real>(),
124                       Real* error = nullptr,
125                       Real* L1 = nullptr,
126                       std::size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
127
128    };
129
130The `tanh-sinh` quadrature routine provided by boost is a rapidly convergent numerical integration scheme for holomorphic integrands.
131By this we mean that the integrand is the restriction to the real line of a complex-differentiable function which is bounded on the interior of the unit disk /|z| < 1/,
132so that it lies within the so-called [@https://en.wikipedia.org/wiki/Hardy_space Hardy space].
133If your integrand obeys these conditions, it can be shown that `tanh-sinh` integration is optimal,
134in the sense that it requires the fewest function evaluations for a given accuracy of any quadrature algorithm for a random element from the Hardy space.
135
136A basic example of how to use the `tanh-sinh` quadrature is shown below:
137
138    tanh_sinh<double> integrator;
139    auto f = [](double x) { return 5*x + 7; };
140    // Integrate over native bounds of (-1,1):
141    double Q = integrator.integrate(f);
142    // Integrate over (0,1.1) instead:
143    Q = integrator.integrate(f, 0.0, 1.1);
144
145The basic idea of `tanh-sinh` quadrature is that a variable transformation can cause the endpoint derivatives to decay rapidly.
146When the derivatives at the endpoints decay much faster than the Bernoulli numbers grow,
147the Euler-Maclaurin summation formula tells us that simple trapezoidal quadrature converges faster than any power of /h/.
148That means the number of correct digits of the result should roughly double with each new level of integration (halving of /h/),
149Hence the default termination condition for integration is usually set to the square root of machine epsilon.
150Most well-behaved integrals should converge to full machine precision with this termination condition,
151and in 6 or fewer levels at double precision, or 7 or fewer levels for quad precision.
152
153One very nice property of tanh-sinh quadrature is that it can handle singularities at the endpoints of the integration domain.
154For instance, the following integrand, singular at both endpoints, can be efficiently evaluated to 100 binary digits:
155
156    auto f = [](Real x) { return log(x)*log1p(-x); };
157    Real Q = integrator.integrate(f, (Real) 0, (Real) 1);
158
159Now onto the caveats: As stated before, the integrands must lie in a Hardy space to ensure rapid convergence.
160Attempting to integrate a function which is not bounded on the unit disk by tanh-sinh can lead to very slow convergence.
161For example, take the Runge function:
162
163    auto f1 = [](double t) { return 1/(1+25*t*t); };
164    Q = integrator.integrate(f1, (double) -1, (double) 1);
165
166This function has poles at \u00B1 \u2148/5, and as such it is not bounded on the unit disk.
167However, the related function
168
169    auto f2 = [](double t) { return 1/(1+0.04*t*t); };
170    Q = integrator.integrate(f2, (double) -1, (double) 1);
171
172has poles outside the unit disk (at \u00B1 5\u2148), and is therefore in the Hardy space.
173Our benchmarks show that the second integration is performed 22x faster than the first!
174If you do not understand the structure of your integrand in the complex plane, do performance testing before deployment.
175
176Like the trapezoidal quadrature, the tanh-sinh quadrature produces an estimate of the L[sub 1] norm of the integral along with the requested integral.
177This is to establish a scale against which to measure the tolerance, and to provide an estimate of the condition number of the summation.
178This can be queried as follows:
179
180    tanh_sinh<double> integrator;
181    auto f = [](double x) { return 5*x + 7; };
182    double termination = std::sqrt(std::numeric_limits<double>::epsilon());
183    double error;
184    double L1;
185    size_t levels;
186    double Q = integrator.integrate(f, 0.0, 1.0, termination, &error, &L1, &levels);
187    double condition_number = L1/std::abs(Q);
188
189If the condition number is large, the computed integral is worthless: typically one can assume that Q has lost one digit of precision
190when the condition number of O(10^Q).
191The returned error term is not the actual error in the result, but merely an a posteriori error estimate.
192It is the absolute difference between the last two approximations, and for well behaved integrals, the actual error should be very much smaller than this.
193The following table illustrates how the errors and conditioning vary for few sample integrals, in each case the termination condition was set
194to the square root of epsilon, and all tests were conducted in double precision:
195
196[table
197[[Integral][Range][Error][Actual measured error][Levels][Condition Number][Comments]]
198[[`5 * x + 7`][(0,1)][3.5e-15][0][5][1][This trivial case shows just how accurate these methods can be.]]
199[[`log(x) * log(x)`][0, 1)][0][0][5][1][This is an example of an integral that Gaussian integrators fail to handle.]]
200[[`exp(-x) / sqrt(x)`][(0,+[infin])][8.0e-10][1.1e-15][5][1][Gaussian integrators typically fail to handle the singularities at the endpoints of this one.]]
201[[`x * sin(2 * exp(2 * sin(2 * exp(2 * x))))`][(-1,1)][7.2e-16][4.9e-17][9][1.89][This is a truly horrible integral that oscillates wildly and
202         unpredictably with some very sharp "spikes" in it's graph.  The higher number of levels used reflects the difficulty of sampling the more extreme features.]]
203[[`x == 0 ? 1 : sin(x) / x`][(-[infin], [infin])][3.0e-1][4.0e-1][15][159][This highly oscillatory integral isn't handled at all well by tanh-sinh quadrature: there is so much
204      cancellation in the sum that the result is essentially worthless.  The argument transformation of the infinite integral behaves somewhat badly as well, in fact
205      we do ['slightly] better integrating over 2 symmetrical and large finite limits.]]
206[[`sqrt(x / (1 - x * x))`][(0,1)][1e-8][1e-8][5][1][This an example of an integral that has all its area close to a non-zero endpoint, the problem here is that
207      the function being integrated returns "garbage" values for x very close to 1.  We can easily fix this issue by passing a 2 argument functor to the integrator:
208      the second argument gives the distance to the nearest endpoint, and we can use that information to return accurate values, and thus fix the integral calculation.]]
209[[`x < 0.5 ? sqrt(x) / sqrt(1 - x * x) : sqrt(x / ((x + 1) * (xc)))`][(0,1)][0][0][5][1][This is the 2-argument version of the previous integral, the second
210      argument ['xc] is `1-x` in this case, and we use 1-x[super 2] == (1-x)(1+x) to calculate 1-x[super 2] with greater accuracy.]]
211]
212
213Although the `tanh-sinh` quadrature can compute integral over infinite domains by variable transformations, these transformations can create a very poorly behaved integrand.
214For this reason, double-exponential variable transformations have been provided that allow stable computation over infinite domains; these being the exp-sinh and sinh-sinh quadrature.
215
216[h4 Complex integrals]
217
218The `tanh_sinh` integrator supports integration of functions which return complex results, for example the sine-integral `Si(z)` has the integral representation:
219
220[equation sine_integral]
221
222Which we can code up directly as:
223
224   template <class Complex>
225   Complex Si(Complex z)
226   {
227      typedef typename Complex::value_type value_type;
228      using std::sin;  using std::cos; using std::exp;
229      auto f = [&z](value_type t) { return -exp(-z * cos(t)) * cos(z * sin(t)); };
230      boost::math::quadrature::tanh_sinh<value_type> integrator;
231      return integrator.integrate(f, 0, boost::math::constants::half_pi<value_type>()) + boost::math::constants::half_pi<value_type>();
232   }
233
234[endsect] [/section:de_tanh_sinh tanh_sinh]
235
236[section:de_tanh_sinh_2_arg Handling functions with large features near an endpoint with tanh-sinh quadrature]
237
238Tanh-sinh quadrature has a unique feature which makes it well suited to handling integrals with either singularities or large features of interest
239near one or both endpoints, it turns out that when we calculate and store the abscissa values at which we will be evaluating the function, we can equally
240well calculate the difference between the abscissa value and the nearest endpoint.
241This makes it possible to perform quadrature arbitrarily close to an endpoint, without suffering cancellation error.
242Note however, that we never actually reach the endpoint, so any endpoint singularity will always be excluded from the quadrature.
243
244The tanh_sinh integration routine will use this additional information internally when performing range transformation, so that for example,
245if one end of the range is zero (or infinite) then our transformations will get arbitrarily close to the endpoint without precision loss.
246
247However, there are some integrals which may have all of their area near ['both] endpoints, or else near the non-zero endpoint, and in that situation
248there is a very real risk of loss of precision.  For example:
249
250    tanh_sinh<double> integrator;
251    auto f = [](double x) { return sqrt(x / (1 - x * x); };
252    double Q = integrator.integrate(f, 0.0, 1.0);
253
254Results in very low accuracy as all the area of the integral is near 1, and the `1 - x * x` term suffers from cancellation error here.
255
256However, both of tanh_sinh's integration routines will automatically handle 2 argument functors: in this case the first argument is the abscissa value as
257before, while the second is the distance to the nearest endpoint, ie `a - x` or `b - x` if we are integrating over (a,b).
258You can always differentiate between these 2 cases because the second argument will be negative if we are nearer to the left endpoint.
259
260Knowing this, we can rewrite our lambda expression to take advantage of this additional information:
261
262    tanh_sinh<double> integrator;
263    auto f = [](double x, double xc) { return x <= 0.5 ? sqrt(x) / sqrt(1 - x * x) : sqrt(x / ((x + 1) * (xc))); };
264    double Q = integrator.integrate(f, 0.0, 1.0);
265
266Not only is this form accurate to full machine-precision, but it converges to the result faster as well.
267
268[endsect] [/section:de_tanh_sinh_2_arg Handling functions with large features near an endpoint with tanh-sinh quadrature]
269
270[section:de_sinh_sinh sinh_sinh]
271
272    template<class Real>
273    class sinh_sinh
274    {
275    public:
276        sinh_sinh(size_t max_refinements = 9);
277
278        template<class F>
279        auto integrate(const F f,
280                       Real tol = sqrt(std::numeric_limits<Real>::epsilon()),
281                       Real* error = nullptr,
282                       Real* L1 = nullptr,
283                       size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;;
284    };
285
286The sinh-sinh quadrature allows computation over the entire real line, and is called as follows:
287
288    sinh_sinh<double> integrator;
289    auto f = [](double x) { return exp(-x*x); };
290    double error;
291    double L1;
292    double Q = integrator.integrate(f, &error, &L1);
293
294Note that the limits of integration are understood to be (-[infin], +[infin]).
295
296Complex valued integrands are supported as well, for example the [@https://en.wikipedia.org/wiki/Dirichlet_eta_function Dirichlet Eta function]
297can be represented via:
298
299[equation complex_eta_integral]
300
301which we can directly code up as:
302
303   template <class Complex>
304   Complex eta(Complex s)
305   {
306      typedef typename Complex::value_type value_type;
307      using std::pow;  using std::exp;
308      Complex i(0, 1);
309      value_type pi = boost::math::constants::pi<value_type>();
310      auto f = [&, s, i](value_type t) { return pow(0.5 + i * t, -s) / (exp(pi * t) + exp(-pi * t)); };
311      boost::math::quadrature::sinh_sinh<value_type> integrator;
312      return integrator.integrate(f);
313   }
314
315
316[endsect] [/section:de_sinh_sinh sinh_sinh]
317
318[section:de_exp_sinh exp_sinh]
319
320    template<class Real>
321    class exp_sinh
322    {
323    public:
324        exp_sinh(size_t max_refinements = 9);
325
326        template<class F>
327        auto integrate(const F f, Real a, Real b,
328                       Real tol = sqrt(std::numeric_limits<Real>::epsilon()),
329                       Real* error = nullptr,
330                       Real* L1 = nullptr,
331                       size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
332        template<class F>
333        auto integrate(const F f,
334                       Real tol = sqrt(std::numeric_limits<Real>::epsilon()),
335                       Real* error = nullptr,
336                       Real* L1 = nullptr,
337                       size_t* levels = nullptr)->decltype(std::declval<F>()(std::declval<Real>())) const;
338    };
339
340For half-infinite intervals, the `exp-sinh` quadrature is provided:
341
342    exp_sinh<double> integrator;
343    auto f = [](double x) { return exp(-3*x); };
344    double termination = sqrt(std::numeric_limits<double>::epsilon());
345    double error;
346    double L1;
347    double Q = integrator.integrate(f, termination, &error, &L1);
348
349
350The native integration range of this integrator is (0, [infin]), but we also support /(a, [infin]), (-[infin], 0)/ and /(-[infin], b)/ via argument transformations.
351
352Endpoint singularities and complex-valued integrands are supported by `exp-sinh`.
353
354For example, the modified Bessel function K can be represented via:
355
356[equation complex_bessel_k_integral]
357
358Which we can code up as:
359
360   template <class Complex>
361   Complex bessel_K(Complex alpha, Complex z)
362   {
363      typedef typename Complex::value_type value_type;
364      using std::cosh;  using std::exp;
365      auto f = [&, alpha, z](value_type t)
366      {
367         value_type ct = cosh(t);
368         if (ct > log(std::numeric_limits<value_type>::max()))
369            return Complex(0);
370         return exp(-z * ct) * cosh(alpha * t);
371      };
372      boost::math::quadrature::exp_sinh<value_type> integrator;
373      return integrator.integrate(f);
374   }
375
376The only wrinkle in the above code is the need to check for large `cosh(t)` in which case we assume that
377`exp(-x cosh(t))` tends to zero faster than `cosh(alpha x)` tends to infinity and return `0`.  Without that
378check we end up with `0 * Infinity` as the result (a NaN).
379
380[endsect] [/section:de_exp_sinh exp_sinh]
381
382[section:de_tol Setting the Termination Condition for Integration]
383
384The integrate method for all three double-exponential quadratures supports ['tolerance] argument that acts as the
385termination condition for integration.
386
387The tolerance is met when two subsequent estimates of the integral have absolute error less than `tolerance*L1`.
388
389It is highly recommended that the tolerance be left at the default value of [radic][epsilon], or something similar.
390Since double exponential quadrature converges exponentially fast for functions in Hardy spaces, then once the routine has *proved* that the error is ~[radic][epsilon],
391then the error should in fact be ~[epsilon].
392
393If you request that the error be ~[epsilon], this tolerance might never be achieved (as the summation is not stabilized ala Kahan),
394and the routine will simply flounder,
395dividing the interval in half in order to increase the precision of the integrand, only to be thwarted by floating point roundoff.
396
397If for some reason, the default value doesn't quite achieve full precision, then you could try something a little smaller such as
398[radic][epsilon]/4 or [epsilon][super 2/3].
399However, more likely, you need to check that your function to be integrated is able to return accurate values, and that there are no other issues with your integration scheme.
400
401[endsect] [/section:de_tol Setting the Termination Condition for Integration]
402
403[section:de_levels Setting the Maximum Interval Halvings and Memory Requirements]
404
405The max interval halvings is the maximum number of times the interval can be cut in half before giving up.
406If the accuracy is not met at that time, the routine returns its best estimate, along with the `error` and `L1`,
407which allows the user to decide if another quadrature routine should be employed.
408
409An example of this is
410
411    double tol = std::sqrt(std::numeric_limits<double>::epsilon());
412    size_t max_halvings = 12;
413    tanh_sinh<double> integrator(max_halvings);
414    auto f = [](double x) { return 5*x + 7; };
415    double error, L1;
416    double Q = integrator.integrate(f, (double) 0, (double) 1, &error, &L1);
417    if (error*L1 > 0.01)
418    {
419        Q = some_other_quadrature_method(f, (double) 0, (double) 1);
420    }
421
422It's important to remember that the number of sample points doubles with each new level, as does the memory footprint
423of the integrator object.  Further, if the integral is smooth, then the precision will be doubling with each new level,
424so that for example, many integrals can achieve 100 decimal digit precision after just 7 levels.  That said, abscissa-weight
425pairs for new levels are computed only when a new level is actually required (see thread safety), none the less,
426you should avoid setting the maximum arbitrarily high "just in case" as the time and space requirements for a large
427number of levels can quickly grow out of control.
428
429[endsect] [/section:de_levels Setting the Maximum Interval Halvings and Memory Requirements]
430
431[section:de_thread Thread Safety]
432
433All three of the double-exponential integrators are thread safe as long as BOOST_MATH_NO_ATOMIC_INT is not set.  Since the
434integrators store a large amount of fairly hard to compute data, it is recommended that these objects are stored and reused
435as much as possible.
436
437Internally all three of the double-exponential integrators use the same caching strategy: they allocate all the vectors needed
438to store the maximum permitted levels, but only populate the first few levels when constructed.  This means a minimal amount of memory
439is actually allocated when the integrator is first constructed, and already populated levels can be accessed via a lockfree
440atomic read, and only populating new levels requires a thread lock.
441
442In addition, the three built in types (plus `__float128` when available), have the first 7 levels pre-computed: this is generally sufficient for the vast majority
443of integrals - even at quad precision - and means that integrators for these types are relatively cheap to construct.
444
445[endsect] [/section:de_thread Thread Safety]
446
447[section:de_caveats Caveats]
448
449A few things to keep in mind while using the tanh-sinh, exp-sinh, and sinh-sinh quadratures:
450
451These routines are *very* aggressive about approaching the endpoint singularities.
452This allows lots of significant digits to be extracted, but also has another problem: Roundoff error can cause the function to be evaluated at the endpoints.
453A few ways to avoid this: Narrow up the bounds of integration to say, [a + [epsilon], b - [epsilon]], make sure (a+b)/2 and (b-a)/2 are representable, and finally,
454if you think the compromise between accuracy an usability has gone too far in the direction of accuracy, file a ticket.
455
456Both exp-sinh and sinh-sinh quadratures evaluate the functions they are passed at *very* large argument.
457You might understand that x[super 12]exp(-x) is should be zero when x[super 12] overflows, but IEEE floating point arithmetic does not.
458Hence `std::pow(x, 12)*std::exp(-x)` is an indeterminate form whenever `std::pow(x, 12)` overflows.
459So make sure your functions have the correct limiting behavior; for example
460
461    auto f = [](double x) {
462        double t = exp(-x);
463        if(t == 0)
464        {
465            return 0;
466        }
467        return t*pow(x, 12);
468    };
469
470has the correct behavior for large /x/, but `auto f = [](double x) { return exp(-x)*pow(x, 12); };` does not.
471
472Oscillatory integrals, such as the sinc integral, are poorly approximated by double-exponential quadrature.
473Fortunately the error estimates and L1 norm are massive for these integrals, but nonetheless, oscillatory integrals require different techniques.
474
475A special mention should be made about integrating through zero: while our range adaptors preserve precision when one endpoint is zero,
476things get harder when the origin is neither in the center of the range, nor at an endpoint.  Consider integrating:
477
478[expression 1 / (1 +x^2)]
479
480Over (a, [infin]).  As long as `a >= 0` both the tanh_sinh and the exp_sinh integrators will handle this just fine: in fact they provide
481a rather efficient method for this kind of integral.  However, if we have `a < 0` then we are forced to adapt the range in a way that
482produces abscissa values near zero that have an absolute error of [epsilon], and since all of the area of the integral is near zero
483both integrators thrash around trying to reach the target accuracy, but never actually get there for `a << 0`.  On the other hand, the
484simple expedient of breaking the integral into two domains: (a, 0) and (0, b) and integrating each separately using the tanh-sinh
485integrator, works just fine.
486
487Finally, some endpoint singularities are too strong to be handled by `tanh_sinh` or equivalent methods, for example consider integrating
488the function:
489
490   double p = some_value;
491   tanh_sinh<double> integrator;
492   auto f = [&](double x){ return pow(tan(x), p); };
493   auto Q = integrator.integrate(f, 0, constants::half_pi<double>());
494
495The first problem with this function, is that the singularity is at [pi]/2, so if we're integrating over (0, [pi]/2) then we can never
496approach closer to the singularity than [epsilon], and for p less than but close to 1, we need to get ['very] close to the singularity
497to find all the area under the function.  If we recall the identity [^tan([pi]/2 - x) == 1/tan(x)] then we can rewrite the function like this:
498
499   auto f = [&](double x){ return pow(tan(x), -p); };
500
501And now the singularity is at the origin and we can get much closer to it when evaluating the integral: all we have done is swap the
502integral endpoints over.
503
504This actually works just fine for p < 0.95, but after that the `tanh_sinh` integrator starts thrashing around and is unable to
505converge on the integral.  The problem is actually a lack of exponent range: if we simply swap type double for something
506with a greater exponent range (an 80-bit long double or a quad precision type), then we can get to at least p = 0.99.  If we want to go
507beyond that, or stick with type double, then we have to get smart.
508
509The easiest method is to notice that for small x, then [^tan(x) [cong] x], and so we are simply integrating x[super -p].  Therefore we can use
510this approximation over (0, small), and integrate numerically from (small, [pi]/2), using [epsilon] as a suitable crossover point
511seems sensible:
512
513   double p = some_value;
514   double crossover = std::numeric_limits<double>::epsilon();
515   tanh_sinh<double> integrator;
516   auto f = [&](double x){ return pow(tan(x), p); };
517   auto Q = integrator.integrate(f, crossover, constants::half_pi<double>()) + pow(crossover, 1 - p) / (1 - p);
518
519There is an alternative, more complex method, which is applicable when we are dealing with expressions which can be simplified
520by evaluating by logs.  Let's suppose that as in this case, all the area under the graph is infinitely close to zero, now imagine
521that we could expand that region out over a much larger range of abscissa values: that's exactly what happens if we perform
522argument substitution, replacing `x` by `exp(-x)` (note that we must also multiply by the derivative of `exp(-x)`).
523Now the singularity at zero is moved to +[infin], and the [pi]/2 bound to
524-log([pi]/2).  Initially our argument substituted function looks like:
525
526   auto f = [&](double x){ return exp(-x) * pow(tan(exp(-x)), -p); };
527
528Which is hardly any better, as we still run out of exponent range just as before.  However, if we replace `tan(exp(-x))` by `exp(-x)` for suitably
529small `exp(-x)`, and therefore [^x > -log([epsilon])], we can greatly simplify the expression and evaluate by logs:
530
531   auto f = [&](double x)
532   {
533      static const double crossover = -log(std::numeric_limits<double>::epsilon());
534      return x > crossover ? exp((p - 1) * x) : exp(-x) * pow(tan(exp(-x)), -p);
535   };
536
537This form integrates just fine over (-log([pi]/2), +[infin]) using either the `tanh_sinh` or `exp_sinh` classes.
538
539[endsect] [/section:de_caveats Caveats]
540
541[section:de_refes References]
542
543* Hidetosi Takahasi and Masatake Mori, ['Double Exponential Formulas for Numerical Integration] Publ. Res. Inst. Math. Sci., 9 (1974), pp. 721-741.
544* Masatake Mori, ['An IMT-Type Double Exponential Formula for Numerical Integration], Publ RIMS, Kyoto Univ. 14 (1978), 713-729.
545* David H. Bailey, Karthik Jeyabalan and Xiaoye S. Li ['A Comparison of Three High-Precision Quadrature Schemes]  Office of Scientific & Technical Information Technical Reports.
546* Tanaka, Ken’ichiro, et al. ['Function classes for double exponential integration formulas.] Numerische Mathematik 111.4 (2009): 631-655.
547
548[endsect] [/section:de_refes References]
549
550[endsect] [/section:double_exponential Double-exponential quadrature]
551