GammaLib 2.0.0
Loading...
Searching...
No Matches
GIntegral.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GIntegral.cpp - Integration class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2010-2020 by Juergen Knoedlseder *
5 * ----------------------------------------------------------------------- *
6 * *
7 * This program is free software: you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation, either version 3 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19 * *
20 ***************************************************************************/
21/**
22 * @file GIntegral.cpp
23 * @brief Integration class implementation
24 * @author Juergen Knoedlseder
25 */
26
27/* __ Includes ___________________________________________________________ */
28#include <cmath> // std::abs()
29#include <vector>
30#include <algorithm> // std::sort
31#include "GIntegral.hpp"
32#include "GException.hpp"
33#include "GTools.hpp"
34#include "GFunction.hpp"
35
36/* __ Method name definitions ____________________________________________ */
37#define G_ROMBERG "GIntegral::romberg(double&, double&, int&)"
38#define G_TRAPZD "GIntegral::trapzd(double&, double&, int&, double)"
39#define G_POLINT "GIntegral::polint(double*, double*, int, double, double*)"
40
41/* __ Macros _____________________________________________________________ */
42
43/* __ Coding definitions _________________________________________________ */
44
45/* __ Debug definitions __________________________________________________ */
46
47/* __ Constants __________________________________________________________ */
48namespace gammalib {
49
50 // Gauss-Kronrod abscissae, common to the 10-, 21-, 43- and 87-point rule
51 const double gkx1[5] = {
52 0.973906528517171720077964012084452,
53 0.865063366688984510732096688423493,
54 0.679409568299024406234327365114874,
55 0.433395394129247190799265943165784,
56 0.148874338981631210884826001129720
57 };
58
59 // Gauss-Kronrod weights of the 10-point rule
60 const double gkw10[5] = {
61 0.066671344308688137593568809893332,
62 0.149451349150580593145776339657697,
63 0.219086362515982043995534934228163,
64 0.269266719309996355091226921569469,
65 0.295524224714752870173892994651338
66 };
67
68 // Gauss-Kronrod abscissae, common to the 21-, 43- and 87-point rule
69 const double gkx2[5] = {
70 0.995657163025808080735527280689003,
71 0.930157491355708226001207180059508,
72 0.780817726586416897063717578345042,
73 0.562757134668604683339000099272694,
74 0.294392862701460198131126603103866
75 };
76
77 // Gauss-Kronrod weights of the 21-point rule for abscissae gkx1
78 const double gkw21a[5] = {
79 0.032558162307964727478818972459390,
80 0.075039674810919952767043140916190,
81 0.109387158802297641899210590325805,
82 0.134709217311473325928054001771707,
83 0.147739104901338491374841515972068
84 };
85
86 // Gauss-Kronrod weights of the 21-point rule for abscissae gkx2
87 const double gkw21b[6] = {
88 0.011694638867371874278064396062192,
89 0.054755896574351996031381300244580,
90 0.093125454583697605535065465083366,
91 0.123491976262065851077958109831074,
92 0.142775938577060080797094273138717,
93 0.149445554002916905664936468389821
94 };
95
96 // Gauss-Kronrod abscissae, common to the 43- and 87-point rule
97 const double gkx3[11] = {
98 0.999333360901932081394099323919911,
99 0.987433402908088869795961478381209,
100 0.954807934814266299257919200290473,
101 0.900148695748328293625099494069092,
102 0.825198314983114150847066732588520,
103 0.732148388989304982612354848755461,
104 0.622847970537725238641159120344323,
105 0.499479574071056499952214885499755,
106 0.364901661346580768043989548502644,
107 0.222254919776601296498260928066212,
108 0.074650617461383322043914435796506
109 };
110
111 // Gauss-Kronrod weights of the 43-point rule for abscissae gkx1, gkx3
112 const double gkw43a[10] = {
113 0.016296734289666564924281974617663,
114 0.037522876120869501461613795898115,
115 0.054694902058255442147212685465005,
116 0.067355414609478086075553166302174,
117 0.073870199632393953432140695251367,
118 0.005768556059769796184184327908655,
119 0.027371890593248842081276069289151,
120 0.046560826910428830743339154433824,
121 0.061744995201442564496240336030883,
122 0.071387267268693397768559114425516
123 };
124
125 // Gauss-Kronrod weights of the 43-point formula for abscissae gkx3
126 const double gkw43b[12] = {
127 0.001844477640212414100389106552965,
128 0.010798689585891651740465406741293,
129 0.021895363867795428102523123075149,
130 0.032597463975345689443882222526137,
131 0.042163137935191811847627924327955,
132 0.050741939600184577780189020092084,
133 0.058379395542619248375475369330206,
134 0.064746404951445885544689259517511,
135 0.069566197912356484528633315038405,
136 0.072824441471833208150939535192842,
137 0.074507751014175118273571813842889,
138 0.074722147517403005594425168280423
139 };
140
141 // Gauss-Kronrod abscissae, of the 87-point rule
142 const double gkx4[22] = {
143 0.999902977262729234490529830591582,
144 0.997989895986678745427496322365960,
145 0.992175497860687222808523352251425,
146 0.981358163572712773571916941623894,
147 0.965057623858384619128284110607926,
148 0.943167613133670596816416634507426,
149 0.915806414685507209591826430720050,
150 0.883221657771316501372117548744163,
151 0.845710748462415666605902011504855,
152 0.803557658035230982788739474980964,
153 0.757005730685495558328942793432020,
154 0.706273209787321819824094274740840,
155 0.651589466501177922534422205016736,
156 0.593223374057961088875273770349144,
157 0.531493605970831932285268948562671,
158 0.466763623042022844871966781659270,
159 0.399424847859218804732101665817923,
160 0.329874877106188288265053371824597,
161 0.258503559202161551802280975429025,
162 0.185695396568346652015917141167606,
163 0.111842213179907468172398359241362,
164 0.037352123394619870814998165437704
165 };
166
167 // Gauss-Kronrod weights of the 87-point rule for abscissae gkx1, gkx2, gkx3
168 const double gkw87a[21] = {
169 0.008148377384149172900002878448190,
170 0.018761438201562822243935059003794,
171 0.027347451050052286161582829741283,
172 0.033677707311637930046581056957588,
173 0.036935099820427907614589586742499,
174 0.002884872430211530501334156248695,
175 0.013685946022712701888950035273128,
176 0.023280413502888311123409291030404,
177 0.030872497611713358675466394126442,
178 0.035693633639418770719351355457044,
179 0.000915283345202241360843392549948,
180 0.005399280219300471367738743391053,
181 0.010947679601118931134327826856808,
182 0.016298731696787335262665703223280,
183 0.021081568889203835112433060188190,
184 0.025370969769253827243467999831710,
185 0.029189697756475752501446154084920,
186 0.032373202467202789685788194889595,
187 0.034783098950365142750781997949596,
188 0.036412220731351787562801163687577,
189 0.037253875503047708539592001191226
190 };
191
192 // Gauss-Kronrod weights of the 87-point formula for abscissae gkx4
193 const double gkw87b[23] = {
194 0.000274145563762072350016527092881,
195 0.001807124155057942948341311753254,
196 0.004096869282759164864458070683480,
197 0.006758290051847378699816577897424,
198 0.009549957672201646536053581325377,
199 0.012329447652244853694626639963780,
200 0.015010447346388952376697286041943,
201 0.017548967986243191099665352925900,
202 0.019938037786440888202278192730714,
203 0.022194935961012286796332102959499,
204 0.024339147126000805470360647041454,
205 0.026374505414839207241503786552615,
206 0.028286910788771200659968002987960,
207 0.030052581128092695322521110347341,
208 0.031646751371439929404586051078883,
209 0.033050413419978503290785944862689,
210 0.034255099704226061787082821046821,
211 0.035262412660156681033782717998428,
212 0.036076989622888701185500318003895,
213 0.036698604498456094498018047441094,
214 0.037120549269832576114119958413599,
215 0.037334228751935040321235449094698,
216 0.037361073762679023410321241766599
217 };
218
219} // end gammalib namespace
220
221
222/*==========================================================================
223 = =
224 = Constructors/destructors =
225 = =
226 ==========================================================================*/
227
228/***********************************************************************//**
229 * @brief Void constructor
230 ***************************************************************************/
232{
233 // Initialise members
234 init_members();
235
236 // Return
237 return;
238}
239
240
241/***********************************************************************//**
242 * @brief Function kernel constructor
243 *
244 * @param[in] kernel Pointer to function kernel.
245 *
246 * The function kernel constructor assigns the function kernel pointer in
247 * constructing the object.
248 ***************************************************************************/
250{
251 // Initialise members
252 init_members();
253
254 // Set function kernel
256
257 // Return
258 return;
259}
260
261
262/***********************************************************************//**
263 * @brief Copy constructor
264 *
265 * @param[in] integral Integral.
266 ***************************************************************************/
268{
269 // Initialise members
270 init_members();
271
272 // Copy members
273 copy_members(integral);
274
275 // Return
276 return;
277}
278
279
280/***********************************************************************//**
281 * @brief Destructor
282 ***************************************************************************/
284{
285 // Free members
286 free_members();
287
288 // Return
289 return;
290}
291
292
293/*==========================================================================
294 = =
295 = Operators =
296 = =
297 ==========================================================================*/
298
299/***********************************************************************//**
300 * @brief Assignment operator
301 *
302 * @param[in] integral Integral.
303 * @return Integral.
304 ***************************************************************************/
306{
307 // Execute only if object is not identical
308 if (this != &integral) {
309
310 // Free members
311 free_members();
312
313 // Initialise integral
314 init_members();
315
316 // Copy members
317 copy_members(integral);
318
319 } // endif: object was not identical
320
321 // Return
322 return *this;
323}
324
325
326/*==========================================================================
327 = =
328 = Public methods =
329 = =
330 ==========================================================================*/
331
332/***********************************************************************//**
333 * @brief Clear integral
334 ***************************************************************************/
336{
337 // Free members
338 free_members();
339
340 // Initialise private members
341 init_members();
342
343 // Return
344 return;
345}
346
347
348/***********************************************************************//**
349 * @brief Clone integral
350 *
351 * @return Pointer to deep copy of integral.
352 ***************************************************************************/
354{
355 return new GIntegral(*this);
356}
357
358
359/***********************************************************************//**
360 * @brief Perform Romberg integration
361 *
362 * @param[in] bounds Integration boundaries.
363 * @param[in] order Integration order (default: 5)
364 *
365 * @exception GException::invalid_argument
366 * Integration order incompatible with number of iterations.
367 *
368 * Returns the integral of the integrand, computed over a number of
369 * intervals [a0,a1], [a1,a2], ... that are given as an unordered vector
370 * by the @p bounds argument.
371 *
372 * Integration is performed by Romberg's method of order 2*order, where
373 *
374 * order=1 is equivalent to the trapezoidal rule,
375 * order=2 is equivalent to Simpson's rule, and
376 * order=3 is equivalent to Boole's rule.
377 *
378 * The number of iterations is limited by m_max_iter. m_eps specifies the
379 * requested fractional accuracy. By default it is set to 1e-6.
380 ***************************************************************************/
381double GIntegral::romberg(std::vector<double> bounds, const int& order)
382{
383 // Sort integration boundaries in ascending order
384 std::sort(bounds.begin(), bounds.end());
385
386 // Initialise integral
387 double value = 0.0;
388
389 // Initialise integration status information
390 int calls = 0;
391
392 // Add integral of all intervals
393 for (int i = 0; i < bounds.size()-1; ++i) {
394 value += romberg(bounds[i], bounds[i+1], order);
395 calls += m_calls;
396 }
397
398 // Set integration status information
399 m_calls = calls;
400
401 // Return value
402 return value;
403}
404
405
406/***********************************************************************//**
407 * @brief Perform Romberg integration
408 *
409 * @param[in] a Left integration boundary.
410 * @param[in] b Right integration boundary.
411 * @param[in] order Integration order (default: 5)
412 *
413 * @exception GException::invalid_argument
414 * Integration order incompatible with number of iterations.
415 *
416 * Returns the integral of the integrand from a to b. Integration is
417 * performed by Romberg's method of order 2*order, where
418 *
419 * order=1 is equivalent to the trapezoidal rule,
420 * order=2 is equivalent to Simpson's rule, and
421 * order=3 is equivalent to Boole's rule.
422 *
423 * The number of iterations is limited by m_max_iter. m_eps specifies the
424 * requested fractional accuracy. By default it is set to 1e-6.
425 ***************************************************************************/
426double GIntegral::romberg(const double& a, const double& b, const int& order)
427{
428 // Initialise result and status
429 double result = 0.0;
430
431 // Initialise integration status information
432 m_isvalid = true;
433 m_calls = 0;
434 m_has_abserr = false;
435 m_has_relerr = false;
436
437 // Continue only if integration range is valid
438 if (b > a) {
439
440 // Initialise variables
441 bool converged = false;
442 double dss = 0.0;
443
444 // Determine (maximum) number of iterations
446
447 // Check whether maximum number of iterations is compliant with
448 // order
449 if (order > max_iter) {
450 std::string msg = "Requested integration order "+
451 gammalib::str(order)+" is larger than the "
452 "maximum number of iterations "+
453 gammalib::str(max_iter)+". Either reduce the "
454 "integration order or increase the (maximum) "
455 "number of iterations.";
457 }
458
459 // Allocate temporal storage
460 double* s = new double[max_iter+2];
461 double* h = new double[max_iter+2];
462
463 // Initialise step size
464 h[1] = 1.0;
465 s[0] = 0.0;
466
467 // Iterative loop
468 for (m_iter = 1; m_iter <= max_iter; ++m_iter) {
469
470 // Integration using Trapezoid rule
471 s[m_iter] = trapzd(a, b, m_iter, s[m_iter-1]);
472
473 // Compile option: Check for NaN/Inf
474 #if defined(G_NAN_CHECK)
475 if (is_notanumber(s[m_iter]) || is_infinite(s[m_iter])) {
476 m_message = "*** ERROR: GIntegral::romberg"
477 "(a="+gammalib::str(a)+", b="+gammalib::str(b)+""
478 ", k="+gammalib::str(k)+"): NaN/Inf encountered"
479 " (s["+gammalib::str(m_iter)+"]="
480 ""+gammalib::str(s[m_iter])+")";
481 std::cout << m_message << std::endl;
482 m_isvalid = false;
483 }
484 #endif
485
486 // Starting from iteration order on, use polynomial interpolation
487 if (m_iter >= order) {
488
489 // Compute result using polynom interpolation
490 result = polint(&h[m_iter-order], &s[m_iter-order],
491 order, 0.0, &dss);
492
493 // If a fixed number of iterations has been requested then
494 // check whether we reached the final one; otherwise check
495 // whether we reached the requested precision.
496 if (m_fix_iter > 0) {
497 if (m_iter == max_iter) {
498 converged = true;
499 }
500 }
501 else if (std::abs(dss) <= m_eps * std::abs(result)) {
502 converged = true;
503 }
504 if (converged) {
505 m_has_abserr = true;
506 m_abserr = std::abs(dss);
507 if (std::abs(result) > 0) {
508 m_has_relerr = true;
509 m_relerr = m_abserr / std::abs(result);
510 }
511 break;
512 }
513
514 } // endif: polynomial interpolation performed
515
516 // Reduce step size
517 h[m_iter+1]= 0.25 * h[m_iter];
518
519 } // endfor: iterative loop
520
521 // Free temporal storage
522 delete [] s;
523 delete [] h;
524
525 // Set status and optionally dump warning
526 if (!converged) {
527 m_isvalid = false;
528 m_message = "Integration uncertainty "+
529 gammalib::str(std::abs(dss))+
530 " exceeds absolute tolerance of "+
531 gammalib::str(m_eps * std::abs(result))+
532 " after "+gammalib::str(m_iter)+
533 " iterations. Result "+
534 gammalib::str(result)+
535 " is inaccurate.";
536 if (!m_silent) {
537 std::string origin = "GIntegral::romberg("+
538 gammalib::str(a)+", "+
539 gammalib::str(b)+", "+
540 gammalib::str(order)+")";
542 }
543 }
544
545 } // endif: integration range was valid
546
547 // Return result
548 return result;
549}
550
551
552/***********************************************************************//**
553 * @brief Perform Trapezoidal integration
554 *
555 * @param[in] a Left integration boundary.
556 * @param[in] b Right integration boundary.
557 * @param[in] n Number of steps.
558 * @param[in] result Result from a previous trapezoidal integration step.
559 * @return Integration results.
560 *
561 * @exception GException::invalid_value
562 * Function kernel not set.
563 *
564 * The method performs a trapezoidal integration of the function kernel for
565 * the integration interval [a,b].
566 *
567 * If @p n = 1 the integral is computed using
568 *
569 * \f[
570 * \int_a^b f(x) \, dx = \frac{1}{2} (b-a) (f(a) + f(b))
571 * \f]
572 *
573 * For @p n > 1 the integral is computed using
574 *
575 * \f[
576 * \int_a^b f(x) \, dx = \frac{1}{2} \left[{\tt result} +
577 * \frac{b-a}{2^{n-1}}
578 * \sum_{i=0}^{2^{n-1}-1} f \left( a + (0.5+i) \frac{b-a}{2^{n-1}} \right) \right]
579 *
580 * \f]
581 *
582 * where \f${\tt result}\f$ is the integration result from a previous call
583 * to the method with @p n = @p n - 1.
584 ***************************************************************************/
585double GIntegral::trapzd(const double& a, const double& b, const int& n,
586 double result)
587{
588 // Throw an exception if the instance has no kernel
589 if (m_kernel == NULL) {
590 std::string msg = "Function kernel not set. Please set a function "
591 "kernel before calling the method.";
593 }
594
595 // Handle case of identical boundaries
596 if (a == b) {
597 result = 0.0;
598 }
599
600 // ... otherwise use trapeziodal rule
601 else {
602
603 // Case A: Only a single step is requested
604 if (n == 1) {
605
606 // Evaluate integrand at boundaries
607 double y_a = m_kernel->eval(a);
608 double y_b = m_kernel->eval(b);
609 m_calls += 2;
610
611 // Compute result
612 result = 0.5*(b-a)*(y_a + y_b);
613
614 } // endif: only a single step was requested
615
616 // Case B: More than a single step is requested
617 else {
618
619 // Compute step level 2^(n-1)
620 int it = 1;
621 for (int j = 1; j < n-1; ++j) {
622 it <<= 1;
623 }
624
625 // Verify that step level is valid
626 if (it == 0) {
627 m_isvalid = false;
628 m_message = "Invalid step level "+gammalib::str(it)+
629 " encountered for"
630 " a="+gammalib::str(a)+
631 ", b="+gammalib::str(b)+
632 ", n="+gammalib::str(n)+
633 ", result="+gammalib::str(result)+
634 ". Looks like n is too large.";
635 if (!m_silent) {
637 }
638 }
639
640 // Set step size
641 double tnm = double(it);
642 double del = (b-a)/tnm;
643
644 // Verify that step is >0
645 if (del == 0) {
646 m_isvalid = false;
647 m_message = "Invalid step size "+gammalib::str(del)+
648 " encountered for"
649 " a="+gammalib::str(a)+
650 ", b="+gammalib::str(b)+
651 ", n="+gammalib::str(n)+
652 ", result="+gammalib::str(result)+
653 ". Step is too small to make sense.";
654 if (!m_silent) {
656 }
657 }
658
659 // Sum up values
660 double x = a + 0.5*del;
661 double sum = 0.0;
662 for (int j = 0; j < it; ++j, x+=del) {
663
664 // Evaluate integrand
665 double y = m_kernel->eval(x);
666 m_calls++;
667
668 // Add integrand
669 sum += y;
670
671 } // endfor: looped over steps
672
673 // Set result
674 result = 0.5*(result + (b-a)*sum/tnm);
675 }
676
677 } // endelse: trapeziodal rule was applied
678
679 // Return result
680 return result;
681}
682
683
684/***********************************************************************//**
685 * @brief Adaptive Simpson's integration
686 *
687 * @param[in] a Left integration boundary.
688 * @param[in] b Right integration boundary.
689 *
690 * Integrates the function using an adaptive Simpson's rule. The initial
691 * interval [a,b] is split into two sub-intervals [a,c] and [c,b] for which
692 * the integral is computed using
693 *
694 * \f[
695 * \frac{b-a}{6} f(a) + 4f(c) + f(b)
696 * \f]
697 *
698 * where \f$c=(a+b)/2\f$ is the mid-point of interval [a,b]. Each
699 * sub-interval is then recursively divided into sub-interval and the process
700 * is repeated. Dividing of sub-intervals is stopped when the difference
701 * between subsequent intervals falls below the relative tolerance specified
702 * by eps(). The maximum recursion depth is set by the max_iter() method.
703 *
704 * I almost do not dare to confess, but the code has been taken from
705 * http://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method
706 * It's really pretty simple ...
707 ***************************************************************************/
708double GIntegral::adaptive_simpson(const double& a, const double& b) const
709{
710 // Initialise integration status information
711 m_isvalid = true;
712 m_calls = 0;
714 m_has_abserr = false;
715 m_has_relerr = false;
716
717 // Compute mid-point c
718 double c = 0.5*(a + b); //!< Mid-point of interval [a,b]
719 double h = b - a; //!< Length of interval [a,b]
720
721 // Evaluate function at boundaries and mid-point c
722 double fa = m_kernel->eval(a);
723 double fb = m_kernel->eval(b);
724 double fc = m_kernel->eval(c);
725 m_calls += 3;
726
727 // Compute integral using Simpson's rule
728 double S = (h/6.0) * (fa + 4.0*fc + fb);
729
730 // Initialise absolute precision
731 double epsilon = (std::abs(S) > 0) ? m_eps * S : m_eps;
732
733 // Call recursive auxiliary function
734 double value = adaptive_simpson_aux(a, b, epsilon, S, fa, fb, fc, m_max_iter);
735
736 // Deduce the number of iterations from the iteration counter
738
739 // If result is not valid, set and output status message
740 if (!m_isvalid) {
741 m_message = "Integration uncertainty exceeds relative tolerance "
742 "of "+gammalib::str(m_eps)+" and absolute tolerance of "
743 ""+gammalib::str(epsilon)+" after "+gammalib::str(m_iter)+
744 " iterations and "+gammalib::str(m_calls)+" function "
745 "calls. Result "+gammalib::str(value)+" inaccurate.";
746 if (!m_silent) {
747 std::string origin = "GIntegral::adaptive_simpson("+
748 gammalib::str(a)+", "+
749 gammalib::str(b)+")";
751 }
752 }
753
754 // Return result
755 return value;
756}
757
758
759/***********************************************************************//**
760 * @brief Gauss-Kronrod integration
761 *
762 * @param[in] a Left integration boundary.
763 * @param[in] b Right integration boundary.
764 *
765 ***************************************************************************/
766double GIntegral::gauss_kronrod(const double& a, const double& b) const
767{
768 // Initialise integration status information
769 m_isvalid = true;
770 m_iter = 0;
771 m_calls = 0;
772 m_has_abserr = false;
773 m_has_relerr = false;
774
775 // Initialise integration result
776 double result = 0.0;
777
778 // Allocate some arrays
779 double fv1[5];
780 double fv2[5];
781 double fv3[5];
782 double fv4[5];
783 double savfun[21];
784
785 // Main code loop (so that we can exit using break)
786 do {
787
788 // Tolerance check
789 if (m_eps < 1.12e-14) {
790 m_isvalid = false;
791 m_message = "Requested relative tolerance of "+gammalib::str(m_eps)+
792 " cannot be acheived. Please relax the integration "
793 "precision.";
794 if (!m_silent) {
795 std::string origin = "GIntegral::gauss_kronrod("+
796 gammalib::str(a)+", "+
797 gammalib::str(b)+")";
799 }
800 }
801
802 // Compute function at mid-point
803 double h = 0.5 * (b - a);
804 double abs_h = std::abs(h);
805 double c = 0.5 * (b + a);
806 double f_c = m_kernel->eval(c);
807 m_calls++;
808
809 // Compute the integral using the 10- and 21-point formulae
810 m_iter++;
811 double res10 = 0;
812 double res21 = gammalib::gkw21b[5] * f_c;
813 double resabs = gammalib::gkw21b[5] * std::abs(f_c);
814 for (int k = 0; k < 5; ++k) {
815 double x = h * gammalib::gkx1[k];
816 double fval1 = m_kernel->eval(c+x);
817 double fval2 = m_kernel->eval(c-x);
818 double fval = fval1 + fval2;
819 m_calls += 2;
820 res10 += gammalib::gkw10[k] * fval;
821 res21 += gammalib::gkw21a[k] * fval;
822 resabs += gammalib::gkw21a[k] * (std::abs(fval1) + std::abs(fval2));
823 savfun[k] = fval;
824 fv1[k] = fval1;
825 fv2[k] = fval2;
826 }
827 for (int k = 0; k < 5; ++k) {
828 double x = h * gammalib::gkx2[k];
829 double fval1 = m_kernel->eval(c+x);
830 double fval2 = m_kernel->eval(c-x);
831 double fval = fval1 + fval2;
832 m_calls += 2;
833 res21 += gammalib::gkw21b[k] * fval;
834 resabs += gammalib::gkw21b[k] * (std::abs(fval1) + std::abs(fval2));
835 savfun[k+5] = fval;
836 fv3[k] = fval1;
837 fv4[k] = fval2;
838 }
839 resabs *= abs_h;
840 double mean = 0.5 * res21;
841 double resasc = gammalib::gkw21b[5] * std::abs(f_c - mean);
842 for (int k = 0; k < 5; ++k) {
843 resasc += (gammalib::gkw21a[k] *
844 (std::abs(fv1[k] - mean) + std::abs(fv2[k] - mean)) +
846 (std::abs(fv3[k] - mean) + std::abs(fv4[k] - mean)));
847 }
848 resasc *= abs_h ;
849 result = res21 * h;
850 double error = rescale_error((res21 - res10) * h, resabs, resasc);
851
852 // Test for convergence */
853 if (error < m_eps * std::abs(result)) {
854 m_has_abserr = true;
855 m_abserr = error;
856 if (std::abs(result) > 0) {
857 m_has_relerr = true;
858 m_relerr = error / std::abs(result);
859 }
860 break;
861 }
862
863 // Compute the integral using the 43-point formula
864 m_iter++;
865 double res43 = gammalib::gkw43b[11] * f_c;
866 for (int k = 0; k < 10; ++k) {
867 res43 += savfun[k] * gammalib::gkw43a[k];
868 }
869 for (int k = 0; k < 11; ++k) {
870 double x = h * gammalib::gkx3[k];
871 double fval = (m_kernel->eval(c+x) +
872 m_kernel->eval(c-x));
873 m_calls += 2;
874 res43 += fval * gammalib::gkw43b[k];
875 savfun[k+10] = fval;
876 }
877 result = res43 * h;
878
879 // Test for convergence */
880 error = rescale_error((res43 - res21) * h, resabs, resasc);
881 if (error < m_eps * std::abs(result)) {
882 m_has_abserr = true;
883 m_abserr = error;
884 if (std::abs(result) > 0) {
885 m_has_relerr = true;
886 m_relerr = error / std::abs(result);
887 }
888 break;
889 }
890
891 // Compute the integral using the 87-point formula
892 m_iter++;
893 double res87 = gammalib::gkw87b[22] * f_c;
894 for (int k = 0; k < 21; ++k) {
895 res87 += savfun[k] * gammalib::gkw87a[k];
896 }
897 for (int k = 0; k < 22; ++k) {
898 double x = h * gammalib::gkx4[k];
899 res87 += gammalib::gkw87b[k] *
900 (m_kernel->eval(c+x) +
901 m_kernel->eval(c-x));
902 m_calls += 2;
903 }
904 result = res87 * h ;
905
906 // Test for convergence */
907 error = rescale_error ((res87 - res43) * h, resabs, resasc);
908 if (error < m_eps * std::abs(result)) {
909 m_has_abserr = true;
910 m_abserr = error;
911 if (std::abs(result) > 0) {
912 m_has_relerr = true;
913 m_relerr = error / std::abs(result);
914 }
915 break;
916 }
917
918 // Failed to converge
919 m_isvalid = false;
920 m_message = "Integration uncertainty "+gammalib::str(error)+" exceeds "
921 "absolute tolerance of "+
922 gammalib::str(m_eps * std::abs(result))+" after "+
923 gammalib::str(m_iter)+" iterations and "+
924 gammalib::str(m_calls)+" function calls. Result "+
925 gammalib::str(result)+" inaccurate.";
926 if (!m_silent) {
927 std::string origin = "GIntegral::gauss_kronrod("+
928 gammalib::str(a)+", "+
929 gammalib::str(b)+")";
931 }
932
933 } while (false); // end of main loop
934
935 // Return result
936 return result;
937}
938
939
940/***********************************************************************//**
941 * @brief Print integral information
942 *
943 * @param[in] chatter Chattiness.
944 * @return String containing integral information.
945 ***************************************************************************/
946std::string GIntegral::print(const GChatter& chatter) const
947{
948 // Initialise result string
949 std::string result;
950
951 // Continue only if chatter is not silent
952 if (chatter != SILENT) {
953
954 // Append header
955 result.append("=== GIntegral ===");
956
957 // Append information
958 result.append("\n"+gammalib::parformat("Relative precision"));
959 result.append(gammalib::str(eps()));
960 if (m_has_abserr) {
961 result.append("\n"+gammalib::parformat("Absolute error"));
962 result.append(gammalib::str(m_abserr));
963 }
964 if (m_has_relerr) {
965 result.append("\n"+gammalib::parformat("Relative error"));
966 result.append(gammalib::str(m_relerr));
967 }
968 result.append("\n"+gammalib::parformat("Function calls"));
969 result.append(gammalib::str(calls()));
970 result.append("\n"+gammalib::parformat("Iterations"));
971 result.append(gammalib::str(iter()));
972 if (m_fix_iter > 0) {
973 result.append(" (fixed: ");
974 result.append(gammalib::str(fixed_iter()));
975 result.append(")");
976 }
977 else {
978 result.append(" (maximum: ");
979 result.append(gammalib::str(max_iter()));
980 result.append(")");
981 }
982
983 // Append status information
984 result.append("\n"+gammalib::parformat("Status"));
985 if (is_valid()) {
986 result.append("Result accurate.");
987 }
988 else {
989 result.append(message());
990 }
991 if (silent()) {
992 result.append("\n"+gammalib::parformat("Warnings")+"suppressed");
993 }
994 else {
995 result.append("\n"+gammalib::parformat("Warnings"));
996 result.append("in standard output");
997 }
998
999 } // endif: chatter was not silent
1000
1001 // Return result
1002 return result;
1003}
1004
1005
1006/*==========================================================================
1007 = =
1008 = Protected methods =
1009 = =
1010 ==========================================================================*/
1011
1012/***********************************************************************//**
1013 * @brief Initialise class members
1014 ***************************************************************************/
1016{
1017 // Initialise members
1018 m_kernel = NULL;
1019 m_eps = 1.0e-6;
1020 m_max_iter = 20;
1021 m_fix_iter = 0;
1022 m_message.clear();
1023 m_silent = false;
1024
1025 // Initialise results
1026 m_iter = 0;
1027 m_calls = 0;
1028 m_isvalid = true;
1029 m_has_abserr = false;
1030 m_has_relerr = false;
1031 m_abserr = 0.0;
1032 m_relerr = 0.0;
1033
1034 // Return
1035 return;
1036}
1037
1038
1039/***********************************************************************//**
1040 * @brief Copy class members
1041 *
1042 * @param[in] integral Integral.
1043 ***************************************************************************/
1045{
1046 // Copy attributes
1047 m_kernel = integral.m_kernel;
1048 m_eps = integral.m_eps;
1049 m_max_iter = integral.m_max_iter;
1050 m_fix_iter = integral.m_fix_iter;
1051 m_message = integral.m_message;
1052 m_silent = integral.m_silent;
1053
1054 // Copy results
1055 m_iter = integral.m_iter;
1056 m_calls = integral.m_calls;
1057 m_isvalid = integral.m_isvalid;
1058 m_has_abserr = integral.m_has_abserr;
1059 m_has_relerr = integral.m_has_relerr;
1060 m_abserr = integral.m_abserr;
1061 m_relerr = integral.m_relerr;
1062
1063 // Return
1064 return;
1065}
1066
1067
1068/***********************************************************************//**
1069 * @brief Delete class members
1070 ***************************************************************************/
1072{
1073 // Return
1074 return;
1075}
1076
1077
1078/***********************************************************************//**
1079 * @brief Perform Polynomial interpolation
1080 *
1081 * @param[in] xa Pointer to array of X values.
1082 * @param[in] ya Pointer to array of Y values.
1083 * @param[in] n Number of elements in arrays.
1084 * @param[in] x X value at which interpolations should be performed.
1085 * @param[out] dy Error estimate for interpolated values.
1086 *
1087 * Given arrays xa[1,..,n] and ya[1,..,n], and given a value x, this
1088 * method returns a value y, and an error estimate dy. If P(x) is the
1089 * polynomial of degree n-1, then the returned value y=P(x).
1090 *
1091 * @todo Implement exceptions instead of screen dump.
1092 * @todo Use std::vector for xa and ya and start at 0
1093 ***************************************************************************/
1094double GIntegral::polint(double* xa, double* ya, int n, double x, double* dy)
1095{
1096 // Initialise result
1097 double y = 0.0;
1098
1099 // Allocate temporary memory
1100 std::vector<double> c(n, 0.0);
1101 std::vector<double> d(n, 0.0);
1102
1103 // Compute initial distance to first node
1104 double dif = std::abs(x-xa[1]);
1105
1106 // Find index ns of the closest table entry
1107 int ns = 0;
1108 for (int i = 0; i < n; ++i) {
1109 double dift = std::abs(x-xa[i+1]);
1110 if (dift < dif) {
1111 ns = i;
1112 dif = dift;
1113 }
1114 c[i] = ya[i+1];
1115 d[i] = ya[i+1];
1116 }
1117
1118 // Get initial approximation to y
1119 y = ya[ns+1];
1120 ns--;
1121
1122 // Loop over each column of the tableau
1123 for (int m = 1; m < n; ++m) {
1124
1125 // Update current c's and d's
1126 for (int i = 0; i < n-m; ++i) {
1127 double ho = xa[i+1] - x;
1128 double hp = xa[i+m+1] - x;
1129 double w = c[i+1] - d[i];
1130 double den = ho - hp;
1131 if (den == 0.0) {
1132 m_isvalid = false;
1133 m_message = "Invalid step size "+gammalib::str(den)+
1134 " encountered. Two values in xa array are"
1135 " identical.";
1136 if (!m_silent) {
1138 }
1139 }
1140 den = w/den;
1141 d[i] = hp*den;
1142 c[i] = ho*den;
1143 }
1144
1145 // Compute y correction
1146 *dy = (2*(ns+1) < (n-m)) ? c[ns+1] : d[ns--];
1147
1148 // Update y
1149 y += *dy;
1150
1151 } // endfor: looped over columns of tableau
1152
1153 // Return
1154 return y;
1155}
1156
1157
1158/***********************************************************************//**
1159 * @brief Auxiliary function for adaptive Simpson's method
1160 *
1161 * @param[in] a Left integration boundary.
1162 * @param[in] b Right integration boundary.
1163 * @param[in] eps Precision.
1164 * @param[in] S Integral of last computation.
1165 * @param[in] fa Function value at left integration boundary.
1166 * @param[in] fb Function value at right integration boundary.
1167 * @param[in] fc Function value at mid-point of interval [a,b]
1168 * @param[in] bottom Iteration counter (stop when 0)
1169 *
1170 * Implements a recursive auxiliary method for the adative_simpson()
1171 * integrator.
1172 ***************************************************************************/
1173double GIntegral::adaptive_simpson_aux(const double& a, const double& b,
1174 const double& eps, const double& S,
1175 const double& fa, const double& fb,
1176 const double& fc,
1177 const int& bottom) const
1178{
1179 // Store the iteration counter
1180 if (bottom < m_iter) {
1181 m_iter = bottom;
1182 }
1183
1184 // Compute mid-point c bet
1185 double c = 0.5*(a + b); //!< Mid-point of interval [a,b]
1186 double h = b - a; //!< Length of interval [a,b]
1187 double d = 0.5*(a + c); //!< Mid-point of interval [a,c]
1188 double e = 0.5*(c + b); //!< Mid-point of interval [c,b]
1189
1190 // Evaluate function at mid-points d and e
1191 double fd = m_kernel->eval(d);
1192 double fe = m_kernel->eval(e);
1193 m_calls += 2;
1194
1195 // Compute integral using Simpson's rule for the left and right interval
1196 double h12 = h / 12.0;
1197 double Sleft = h12 * (fa + 4.0*fd + fc);
1198 double Sright = h12 * (fc + 4.0*fe + fb);
1199 double S2 = Sleft + Sright;
1200
1201 // Allocate result
1202 double value;
1203
1204 // If converged then compute the result ...
1205 if (std::abs(S2 - S) <= 15.0 * eps) {
1206// if (std::abs(S2 - S) <= 15.0 * m_eps * std::abs(S2)) {
1207 value = S2 + (S2 - S)/15.0;
1208 }
1209
1210 // ... else if the maximum recursion depth was reached then compute the
1211 // result and signal result invalidity
1212 else if (bottom <= 0) {
1213 value = S2 + (S2 - S)/15.0;
1214 m_isvalid = false;
1215 }
1216
1217 // ... otherwise call this method recursively
1218 else {
1219 value = adaptive_simpson_aux(a, c, 0.5*eps, Sleft, fa, fc, fd, bottom-1) +
1220 adaptive_simpson_aux(c, b, 0.5*eps, Sright, fc, fb, fe, bottom-1);
1221 }
1222
1223 // Return result
1224 return value;
1225}
1226
1227
1228/***********************************************************************//**
1229 * @brief Rescale errors for Gauss-Kronrod integration
1230 *
1231 * @param[in] err Error estimate.
1232 * @param[in] result_abs ???.
1233 * @param[in] result_asc ???.
1234 * @return Rescaled error estimate.
1235 ***************************************************************************/
1237 const double& result_abs,
1238 const double& result_asc) const
1239{
1240 // Take absolute value of error
1241 err = std::abs(err);
1242
1243 // ...
1244 if (result_asc != 0.0 && err != 0.0) {
1245 double scale = std::pow((200.0 * err / result_asc), 1.5);
1246 if (scale < 1.0) {
1247 err = result_asc * scale ;
1248 }
1249 else {
1250 err = result_asc ;
1251 }
1252 }
1253 if (result_abs > 2.2250738585072014e-308 / (50.0 * 2.2204460492503131e-16)) {
1254 double min_err = 50.0 * 2.2204460492503131e-16 * result_abs;
1255 if (min_err > err) {
1256 err = min_err ;
1257 }
1258 }
1259
1260 // Return error
1261 return err;
1262}
Exception handler interface definition.
Single parameter function abstract base class definition.
#define G_ROMBERG
Definition GIntegral.cpp:37
#define G_TRAPZD
Definition GIntegral.cpp:38
#define G_POLINT
Definition GIntegral.cpp:39
Integration class interface definition.
Gammalib tools definition.
GChatter
Definition GTypemaps.hpp:33
@ SILENT
Definition GTypemaps.hpp:34
double sum(const GVector &vector)
Computes vector sum.
Definition GVector.cpp:944
Single parameter function abstract base class.
Definition GFunction.hpp:44
virtual double eval(const double &x)=0
GIntegral class interface definition.
Definition GIntegral.hpp:46
const bool & silent(void) const
Get silence flag.
const std::string & message(void) const
Return integration status message.
std::string m_message
Status message (if result is invalid)
int m_calls
Number of function calls used.
const double & eps(void) const
Get relative precision.
double adaptive_simpson_aux(const double &a, const double &b, const double &eps, const double &S, const double &fa, const double &fb, const double &fc, const int &bottom) const
Auxiliary function for adaptive Simpson's method.
const int & iter(void) const
Return number of iterations.
double romberg(std::vector< double > bounds, const int &order=5)
Perform Romberg integration.
double m_relerr
Absolute integration error.
void copy_members(const GIntegral &integral)
Copy class members.
double m_eps
Requested relative integration precision.
const int & fixed_iter(void) const
Return fixed number of iterations.
const int & calls(void) const
Get number of function calls.
bool m_has_abserr
Has absolute integration error.
bool m_has_relerr
Has relative integration error.
GIntegral * clone(void) const
Clone integral.
double trapzd(const double &a, const double &b, const int &n=1, double result=0.0)
Perform Trapezoidal integration.
void clear(void)
Clear integral.
bool m_silent
Suppress integration warnings in console.
const GFunction * kernel(void) const
Get function kernel.
int m_iter
Number of iterations used.
double gauss_kronrod(const double &a, const double &b) const
Gauss-Kronrod integration.
virtual ~GIntegral(void)
Destructor.
int m_max_iter
Maximum number of iterations.
void init_members(void)
Initialise class members.
double adaptive_simpson(const double &a, const double &b) const
Adaptive Simpson's integration.
GFunction * m_kernel
Pointer to function kernel.
std::string print(const GChatter &chatter=NORMAL) const
Print integral information.
GIntegral(void)
Void constructor.
void free_members(void)
Delete class members.
GIntegral & operator=(const GIntegral &integral)
Assignment operator.
const int & max_iter(void) const
Return maximum number of iterations.
double rescale_error(double err, const double &result_abs, const double &result_asc) const
Rescale errors for Gauss-Kronrod integration.
const bool & is_valid(void) const
Signal if integration result is valid.
double m_abserr
Absolute integration error.
bool m_isvalid
Integration result valid (true=yes)
double polint(double *xa, double *ya, int n, double x, double *dy)
Perform Polynomial interpolation.
int m_fix_iter
Fixed number of iterations.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1143
const double gkw43b[12]
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:489
const double gkw43a[10]
const double gkw87b[23]
const double gkx2[5]
Definition GIntegral.cpp:69
const double gkw21b[6]
Definition GIntegral.cpp:87
const double gkw10[5]
Definition GIntegral.cpp:60
const double gkw87a[21]
void warning(const std::string &origin, const std::string &message)
Emits warning.
Definition GTools.cpp:1386
const double gkx1[5]
Definition GIntegral.cpp:51
const double gkx3[11]
Definition GIntegral.cpp:97
const double gkw21a[5]
Definition GIntegral.cpp:78
const double gkx4[22]