1 /** Authors:    Lars Tandle Kyllingstad
2     Copyright:  Copyright (c) 2010–2011, Lars T. Kyllingstad. All rights reserved.
3     License:    Boost License 1.0
4 */
5 module scid.internal.calculus.integrate_qng;
6 
7 
8 import std.conv;
9 import std.exception;
10 import std.math;
11 import std.numeric;
12 import std.traits;
13 
14 import scid.calculus: IntegrationException;
15 import scid.types;
16 import scid.util;
17 
18 version(unittest) import scid.core.testing;
19 
20 
21 
22 
23 Result!Real qng(Func, Real = ReturnType!Func)
24     (Func f, Real a, Real b, Real epsRel, Real epsAbs)
25 {
26     alias FPTemporary!Real T;
27 
28     enum epsRelMin = 50*T.epsilon;
29     enforce(
30         epsAbs > 0 || epsRel > epsRelMin,
31         text("Invalid accuracy request (must have epsAbs > 0 or epsRel > ",
32             epsRelMin, ")"));
33 
34 
35     // This function is used to calculate the absolute error.
36     static T errorCalc(T estimate, T absResult, T variance)
37     {
38         T error = estimate;
39         if (variance != 0 && error != 0)
40             error = variance * fmin(1, (200*error/variance)^^1.5);
41         if (absResult > T.min_normal/(50*T.epsilon))
42             error = fmax(error, 50*T.epsilon*absResult);
43         return error;
44     }
45 
46 
47     // Gauss-Kronrod-Patterson quadrature coefficients,
48     // calculated by L. W. Fullerton, Bell Labs, Nov. 1981.
49     static immutable T[5] x1 = staticArrayOf!T(
50         0.9739065285_1717172007_7964012084_452L,
51         0.8650633666_8898451073_2096688423_493L,
52         0.6794095682_9902440623_4327365114_874L,
53         0.4333953941_2924719079_9265943165_784L,
54         0.1488743389_8163121088_4826001129_720L);
55     static immutable T[5] w10 = staticArrayOf!T(
56         0.0666713443_0868813759_3568809893_332L,
57         0.1494513491_5058059314_5776339657_697L,
58         0.2190863625_1598204399_5534934228_163L,
59         0.2692667193_0999635509_1226921569_469L,
60         0.2955242247_1475287017_3892994651_338L);
61 
62 
63     static immutable T[5] x2 = staticArrayOf!T(
64         0.9956571630_2580808073_5527280689_003L,
65         0.9301574913_5570822600_1207180059_508L,
66         0.7808177265_8641689706_3717578345_042L,
67         0.5627571346_6860468333_9000099272_694L,
68         0.2943928627_0146019813_1126603103_866L);
69     static immutable T[5] w21a = staticArrayOf!T(
70         0.0325581623_0796472747_8818972459_390L,
71         0.0750396748_1091995276_7043140916_190L,
72         0.1093871588_0229764189_9210590325_805L,
73         0.1347092173_1147332592_8054001771_707L,
74         0.1477391049_0133849137_4841515972_068L);
75     static immutable T[6] w21b = staticArrayOf!T(
76         0.0116946388_6737187427_8064396062_192L,
77         0.0547558965_7435199603_1381300244_580L,
78         0.0931254545_8369760553_5065465083_366L,
79         0.1234919762_6206585107_7958109831_074L,
80         0.1427759385_7706008079_7094273138_717L,
81         0.1494455540_0291690566_4936468389_821L);
82 
83     static immutable T[11] x3 = staticArrayOf!T(
84         0.9993333609_0193208139_4099323919_911L,
85         0.9874334029_0808886979_5961478381_209L,
86         0.9548079348_1426629925_7919200290_473L,
87         0.9001486957_4832829362_5099494069_092L,
88         0.8251983149_8311415084_7066732588_520L,
89         0.7321483889_8930498261_2354848755_461L,
90         0.6228479705_3772523864_1159120344_323L,
91         0.4994795740_7105649995_2214885499_755L,
92         0.3649016613_4658076804_3989548502_644L,
93         0.2222549197_7660129649_8260928066_212L,
94         0.0746506174_6138332204_3914435796_506L);
95     static immutable T[10] w43a = staticArrayOf!T(
96         0.0162967342_8966656492_4281974617_663L,
97         0.0375228761_2086950146_1613795898_115L,
98         0.0546949020_5825544214_7212685465_005L,
99         0.0673554146_0947808607_5553166302_174L,
100         0.0738701996_3239395343_2140695251_367L,
101         0.0057685560_5976979618_4184327908_655L,
102         0.0273718905_9324884208_1276069289_151L,
103         0.0465608269_1042883074_3339154433_824L,
104         0.0617449952_0144256449_6240336030_883L,
105         0.0713872672_6869339776_8559114425_516L);
106     static immutable T[12] w43b = staticArrayOf!T(
107         0.0018444776_4021241410_0389106552_965L,
108         0.0107986895_8589165174_0465406741_293L,
109         0.0218953638_6779542810_2523123075_149L,
110         0.0325974639_7534568944_3882222526_137L,
111         0.0421631379_3519181184_7627924327_955L,
112         0.0507419396_0018457778_0189020092_084L,
113         0.0583793955_4261924837_5475369330_206L,
114         0.0647464049_5144588554_4689259517_511L,
115         0.0695661979_1235648452_8633315038_405L,
116         0.0728244414_7183320815_0939535192_842L,
117         0.0745077510_1417511827_3571813842_889L,
118         0.0747221475_1740300559_4425168280_423L);
119 
120     static immutable T[22] x4 = staticArrayOf!T(
121         0.9999029772_6272923449_0529830591_582L,
122         0.9979898959_8667874542_7496322365_960L,
123         0.9921754978_6068722280_8523352251_425L,
124         0.9813581635_7271277357_1916941623_894L,
125         0.9650576238_5838461912_8284110607_926L,
126         0.9431676131_3367059681_6416634507_426L,
127         0.9158064146_8550720959_1826430720_050L,
128         0.8832216577_7131650137_2117548744_163L,
129         0.8457107484_6241566660_5902011504_855L,
130         0.8035576580_3523098278_8739474980_964L,
131         0.7570057306_8549555832_8942793432_020L,
132         0.7062732097_8732181982_4094274740_840L,
133         0.6515894665_0117792253_4422205016_736L,
134         0.5932233740_5796108887_5273770349_144L,
135         0.5314936059_7083193228_5268948562_671L,
136         0.4667636230_4202284487_1966781659_270L,
137         0.3994248478_5921880473_2101665817_923L,
138         0.3298748771_0618828826_5053371824_597L,
139         0.2585035592_0216155180_2280975429_025L,
140         0.1856953965_6834665201_5917141167_606L,
141         0.1118422131_7990746817_2398359241_362L,
142         0.0373521233_9461987081_4998165437_704L);
143     static immutable T[21] w87a = staticArrayOf!T(
144         0.0081483773_8414917290_0002878448_190L,
145         0.0187614382_0156282224_3935059003_794L,
146         0.0273474510_5005228616_1582829741_283L,
147         0.0336777073_1163793004_6581056957_588L,
148         0.0369350998_2042790761_4589586742_499L,
149         0.0028848724_3021153050_1334156248_695L,
150         0.0136859460_2271270188_8950035273_128L,
151         0.0232804135_0288831112_3409291030_404L,
152         0.0308724976_1171335867_5466394126_442L,
153         0.0356936336_3941877071_9351355457_044L,
154         0.0009152833_4520224136_0843392549_948L,
155         0.0053992802_1930047136_7738743391_053L,
156         0.0109476796_0111893113_4327826856_808L,
157         0.0162987316_9678733526_2665703223_280L,
158         0.0210815688_8920383511_2433060188_190L,
159         0.0253709697_6925382724_3467999831_710L,
160         0.0291896977_5647575250_1446154084_920L,
161         0.0323732024_6720278968_5788194889_595L,
162         0.0347830989_5036514275_0781997949_596L,
163         0.0364122207_3135178756_2801163687_577L,
164         0.0372538755_0304770853_9592001191_226L);
165     static immutable T[23] w87b = staticArrayOf!T(
166         0.0002741455_6376207235_0016527092_881L,
167         0.0018071241_5505794294_8341311753_254L,
168         0.0040968692_8275916486_4458070683_480L,
169         0.0067582900_5184737869_9816577897_424L,
170         0.0095499576_7220164653_6053581325_377L,
171         0.0123294476_5224485369_4626639963_780L,
172         0.0150104473_4638895237_6697286041_943L,
173         0.0175489679_8624319109_9665352925_900L,
174         0.0199380377_8644088820_2278192730_714L,
175         0.0221949359_6101228679_6332102959_499L,
176         0.0243391471_2600080547_0360647041_454L,
177         0.0263745054_1483920724_1503786552_615L,
178         0.0282869107_8877120065_9968002987_960L,
179         0.0300525811_2809269532_2521110347_341L,
180         0.0316467513_7143992940_4586051078_883L,
181         0.0330504134_1997850329_0785944862_689L,
182         0.0342550997_0422606178_7082821046_821L,
183         0.0352624126_6015668103_3782717998_428L,
184         0.0360769896_2288870118_5500318003_895L,
185         0.0366986044_9845609449_8018047441_094L,
186         0.0371205492_6983257611_4119958413_599L,
187         0.0373342287_5193504032_1235449094_698L,
188         0.0373610737_6267902341_0321241766_599L);
189 
190 
191     // Some useful quantities.
192     immutable T halfLength = (b - a) / 2;
193     immutable T absHalfLength = abs(halfLength);
194 
195     immutable T center = (a + b) / 2;
196     immutable T fCenter = f(center);
197 
198     // In these arrays we save the result of function evaluations,
199     // so they can be reused for higher-order rules.
200     T[5] fSave1, fSave2, fSave3, fSave4;
201     T[21] fSave;
202 
203 
204     // Compute the integral using the 10- and 21-point formulae.
205     T result10 = 0;
206     T result21 = w21b[5] * fCenter;
207     T resultAbs = w21b[5] * abs(fCenter);
208 
209     foreach (k; 0 .. 5)
210     {
211         immutable x = halfLength * x1[k];
212 
213         immutable fValue1 = f(center + x);
214         immutable fValue2 = f(center - x);
215         immutable fValue = fValue1 + fValue2;
216 
217         result10 += w10[k] * fValue;
218         result21 += w21a[k] * fValue;
219         resultAbs += w21a[k] * (abs(fValue1) + abs(fValue2));
220 
221         fSave1[k] = fValue1;
222         fSave2[k] = fValue2;
223         fSave[k] = fValue;
224     }
225 
226     foreach (k; 0 .. 5)
227     {
228         immutable x = halfLength * x2[k];
229 
230         immutable fValue1 = f(center + x);
231         immutable fValue2 = f(center - x);
232         immutable fValue = fValue1 + fValue2;
233 
234         result21 += w21b[k] * fValue;
235         resultAbs += w21b[k] * (abs(fValue1) + abs(fValue2));
236 
237         fSave3[k] = fValue1;
238         fSave4[k] = fValue2;
239         fSave[k+5] = fValue;
240     }
241 
242     // resultAbs is the absolute value of the integral of the
243     // absolute value of the function.
244     resultAbs *= absHalfLength;
245 
246     // fMean is the mean value of f(x) in the interval [a, b],
247     // as evaluated by the 21-point rule.
248     immutable fMean = result21/2;
249 
250     // variance is the integral of f(x)-fMean on the interval [a, b],
251     // evaluated using the 21-point rule.
252     T variance = w21b[5] * abs(fCenter - fMean);
253     foreach (k; 0 .. 5)
254     {
255         variance +=
256             w21a[k] * (abs(fSave1[k] - fMean) + abs(fSave2[k] - fMean))
257             + w21b[k] * (abs(fSave3[k] - fMean) + abs(fSave4[k] - fMean));
258     }
259     variance *= absHalfLength;
260 
261     // Test for convergence.
262     {
263 
264         immutable result = result21 * halfLength;
265         immutable error = errorCalc(abs((result21 - result10)*halfLength),
266             resultAbs, variance);
267 
268         if (error <= fmax(epsAbs, epsRel * abs(result)))
269             return typeof(return)(result, error);
270     }
271 
272 
273     // Compute the integral using the 43-point formula.
274     T result43 = w43b[11] * fCenter;
275     foreach (k; 0 .. 10)
276         result43 += fSave[k] * w43a[k];
277 
278     foreach (k; 0 .. 11)
279     {
280         immutable x = halfLength * x3[k];
281         immutable fValue = f(center+x) + f(center-x);
282         result43 += fValue * w43b[k];
283         fSave[k+10] = fValue;
284     }
285 
286     // Test for convergence
287     {
288         immutable result = result43 * halfLength;
289         immutable error = errorCalc(abs((result43-result21)*halfLength),
290             resultAbs, variance);
291 
292         if (error <= fmax(epsAbs, epsRel * abs(result)))
293             return typeof(return)(result, error);
294     }
295 
296 
297     // Compute the integral using the 87-point formula.
298     T result87 = w87b[22] * fCenter;
299     foreach (k; 0 .. 21)
300         result87 += fSave[k] * w87a[k];
301 
302     foreach (k; 0 .. 22)
303     {
304         immutable x = halfLength * x4[k];
305         result87 += w87b[k] * (f(center+x) + f(center-x));
306     }
307 
308     // Test for convergence
309     {
310         immutable result = result87 * halfLength;
311         immutable error = errorCalc(abs((result87-result43)*halfLength),
312             resultAbs, variance);
313 
314         if (error > fmax(epsAbs, epsRel * abs(result)))
315             throw new IntegrationException(
316                 "The maximum number of steps has been executed",
317                 "The integral is probably too difficult to be calculated "
318                 ~"by the QNG algorithm.",
319                 result, error);
320 
321         return typeof(return)(result, error);
322     }
323 }
324 
325 
326 unittest
327 {
328     // Check that it compiles for different types.
329     alias qng!(float delegate(float)) fqng;
330     alias qng!(double delegate(double)) dqng;
331     alias qng!(double function(double)) dfqng;
332     alias qng!(real delegate(real)) rqng;
333 }
334 
335 
336 unittest
337 {
338     // QUADPACK book §4.4, integral 4
339     double alpha;
340     double f(double x) { return x^^alpha * log(1/x); }
341 
342     enum epsabs = 0.0;
343     enum epsrel = 1e-8;
344 
345     foreach (i; 1 .. 9)
346     {
347         alpha = 1 + i*0.2;
348         auto result = qng(&f, 0.0, 1.0, epsabs, epsrel);
349         auto expect = (alpha + 1)^^(-2);
350         assert (isAccurate(result.value, result.error, expect, epsrel, epsabs));
351     }
352 }
353 
354 
355 unittest
356 {
357     // QUADPACK book §4.4, integral 5
358     double alpha;
359     double f(double x) { return 4^^(-alpha) / ((x-PI_4)^^2 + 16^^(-alpha)); }
360 
361     enum epsabs = 0.0;
362     enum epsrel = 1e-8;
363 
364     foreach (i; 0 .. 2)
365     {
366         alpha = i;
367         auto result = qng(&f, 0.0, 1.0, epsabs, epsrel);
368         double expect = atan((4-PI) * 4^^(alpha-1)) + atan(PI * 4^^(alpha-1));
369         assert (isAccurate(result.value, result.error, expect, epsrel, epsabs));
370     }
371 }
372 
373 
374 unittest
375 {
376     // QUADPACK book §4.4, integral 6
377     double alpha;
378     double f(double x) { return cos(2^^alpha * sin(x)); }
379 
380     enum epsabs = 0.0;
381     enum epsrel = 1e-8;
382 
383     auto expect = [
384          2.4039394306344129983,
385          0.70337362695660089178,
386         -1.2476829250428461076,
387          0.53925691468609779719,
388         -0.54946164594662718058,
389          0.43378800263473354846,
390          0.29088010217372596783
391     ];
392 
393     foreach (i; 0 .. 6)
394     {
395         alpha = i;
396         auto result = qng(&f, 0.0, cast(double) PI, epsabs, epsrel);
397         //assert (isAccurate(result.value, result.error, expect[i], epsrel, epsabs));
398         assert (abs((result.value-expect[i])/result.value) <= epsrel);
399     }
400     // TODO:
401     // Some of the error estimates are a bit too low, which is why we
402     // can't use isAccurate.  This may not be a problem, but it's worth
403     // investigating closer.
404     //
405     // What's worse is that the routine should succeed for i=6 as well,
406     // but it doesn't.  Actually, it does converge to the right value,
407     // but it erroneously reports the error as ~0.8.
408 }