1 /++
2 This module contains betterC compatible quadrature computation routines.
3 +/
4 module mir.quadrature;
5 
6 import mir.math.common: sqrt, exp;
7 import mir.math.constant: PI, LN2;
8 
9 @safe pure nothrow:
10 
11 version(LDC){} else
12 @nogc extern(C)
13 {
14     double lgamma(double);
15     double tgamma(double);
16 }
17 
18 /++
19 Gauss-Hermite Quadrature
20 
21 Params:
22     x = (out) user-allocated quadrature nodes in ascending order length of `N`
23     w = (out) user-allocated corresponding quadrature weights length of `N`
24     work = (temp) user-allocated workspace length of greate or equal to `(N + 1) ^^ 2`
25 
26 Returns: 0 on success, `xSTEQR` LAPACK code on numerical error.
27 
28 +/
29 size_t gaussHermiteQuadrature(T)(
30     scope T[] x,
31     scope T[] w,
32     scope T[] work) @nogc
33 in {
34     assert(x.length == w.length);
35     if (x.length)
36         assert(work.length >= x.length ^^ 2);
37 }
38 do {
39     enum T mu0 = sqrt(PI);
40     foreach (i; 0 .. x.length)
41     {
42         x[i] = 0;
43         w[i] = T(0.5) * i;
44     }
45     return gaussQuadratureImpl!T(x, w, work, mu0, true);
46 }
47 
48 ///
49 unittest
50 {
51     import mir.math.common;
52     import mir.ndslice.allocation;
53 
54     auto n = 5;
55     auto x = new double[n];
56     auto w = new double[n];
57     auto work = new double[(n + 1) ^^ 2];
58 
59     gaussHermiteQuadrature(x, w, work);
60 
61     static immutable xc =
62        [-2.02018287,
63         -0.95857246,
64          0.        ,
65          0.95857246,
66          2.02018287];
67 
68     static immutable wc =
69        [0.01995324,
70         0.39361932,
71         0.94530872,
72         0.39361932,
73         0.01995324];
74 
75     foreach (i; 0 .. n)
76     {
77         assert(x[i].approxEqual(xc[i]));
78         assert(w[i].approxEqual(wc[i]));
79     }
80 }
81 
82 /++
83 Gauss-Jacobi Quadrature
84 
85 Params:
86     x = (out) user-allocated quadrature nodes in ascending order length of `N`
87     w = (out) user-allocated corresponding quadrature weights length of `N`
88     work = (temp) user-allocated workspace length of greate or equal to `(N + 1) ^^ 2`
89     alpha = parameter '> -1'
90     beta = parameter '> -1'
91 
92 Returns: 0 on success, `xSTEQR` LAPACK code on numerical error.
93 
94 +/
95 size_t gaussJacobiQuadrature(T)(
96     scope T[] x,
97     scope T[] w,
98     scope T[] work,
99     T alpha,
100     T beta) @nogc
101 in {
102     assert(T.infinity > alpha && alpha > -1);
103     assert(T.infinity > beta && beta > -1);
104     assert(x.length == w.length);
105     if (x.length)
106         assert(work.length >= x.length ^^ 2);
107 }
108 do {
109     if (x.length == 0)
110         return 0;
111     auto s = alpha + beta;
112     auto d = beta - alpha;
113     version (LDC) import core.stdc.math: lgamma;
114     auto mu0 = exp(double(LN2) * (s + 1) + (lgamma(double(alpha + 1)) + lgamma(double(beta + 1)) - lgamma(double(s + 2))));
115     x[0] = d / (s + 2);
116     const sd = s * d;
117     foreach (i; 1 .. x.length)
118     {
119         const m_i = T(1) / i;
120         const q = (2 + s * m_i);
121         x[i] = sd * m_i ^^ 2 / (q * (2 + (s + 2) * m_i));
122         w[i] = 4 * (1 + alpha * m_i) * (1 + beta * m_i) * (1 + s * m_i)
123             / ((2 + (s + 1) * m_i) * (2 + (s - 1) * m_i) * q ^^ 2);
124     }
125     return gaussQuadratureImpl!T(x, w, work, mu0);
126 }
127 
128 ///
129 unittest
130 {
131     import mir.math.common;
132     import mir.ndslice.allocation;
133 
134     auto n = 5;
135     auto x = new double[n];
136     auto w = new double[n];
137     auto work = new double[(n + 1) ^^ 2];
138 
139     gaussJacobiQuadrature(x, w, work, 2.3, 3.6);
140 
141     static immutable xc =
142        [-0.6553677 ,
143         -0.29480426,
144          0.09956621,
145          0.47584565,
146          0.78356514];
147 
148     static immutable wc =
149        [0.02262392,
150         0.19871672,
151         0.43585107,
152         0.32146619,
153         0.0615342 ];
154 
155     foreach (i; 0 .. n)
156     {
157         assert(x[i].approxEqual(xc[i]));
158         assert(w[i].approxEqual(wc[i]));
159     }
160 }
161 
162 /++
163 Gauss-Laguerre Quadrature
164 
165 Params:
166     x = (out) user-allocated quadrature nodes in ascending order length of `N`
167     w = (out) user-allocated corresponding quadrature weights length of `N`
168     work = (temp) user-allocated workspace length of greate or equal to `(N + 1) ^^ 2`
169     alpha = (optional) parameter '> -1'
170 
171 Returns: 0 on success, `xSTEQR` LAPACK code on numerical error.
172 
173 +/
174 size_t gaussLaguerreQuadrature(T)(
175     scope T[] x,
176     scope T[] w,
177     scope T[] work,
178     T alpha = 0) @nogc
179 in {
180     assert(T.infinity > alpha && alpha > -1);
181     assert(x.length == w.length);
182     if (x.length)
183         assert(work.length >= x.length ^^ 2);
184 }
185 do {
186 
187     version (LDC) import core.stdc.math: tgamma;
188     auto mu0 = tgamma(double(alpha + 1));
189     foreach (i; 0 .. x.length)
190     {
191         x[i] = 2 * i + (1 + alpha);
192         w[i] = i * (i + alpha);
193     }
194     return gaussQuadratureImpl!T(x, w, work, mu0);
195 }
196 
197 ///
198 unittest
199 {
200     import mir.math.common;
201     import mir.ndslice.allocation;
202 
203     auto n = 5;
204     auto x = new double[n];
205     auto w = new double[n];
206     auto work = new double[(n + 1) ^^ 2];
207 
208     gaussLaguerreQuadrature(x, w, work);
209 
210     static immutable xc =
211        [ 0.26356032,
212          1.41340306,
213          3.59642577,
214          7.08581001,
215         12.64080084];
216 
217     static immutable wc =
218        [5.21755611e-01,
219         3.98666811e-01,
220         7.59424497e-02,
221         3.61175868e-03,
222         2.33699724e-05];
223 
224     foreach (i; 0 .. n)
225     {
226         assert(x[i].approxEqual(xc[i]));
227         assert(w[i].approxEqual(wc[i]));
228     }
229 }
230 
231 /++
232 Gauss-Legendre Quadrature
233 
234 Params:
235     x = (out) user-allocated quadrature nodes in ascending order length of `N`
236     w = (out) user-allocated corresponding quadrature weights length of `N`
237     work = (temp) user-allocated workspace length of greate or equal to `(N + 1) ^^ 2`
238 
239 Returns: 0 on success, `xSTEQR` LAPACK code on numerical error.
240 
241 +/
242 size_t gaussLegendreQuadrature(T)(
243     scope T[] x,
244     scope T[] w,
245     scope T[] work) @nogc
246 in {
247     assert(x.length == w.length);
248     if (x.length)
249         assert(work.length >= x.length ^^ 2);
250 }
251 do {
252     if (x.length == 0)
253         return 0;
254     enum mu0 = 2;
255     x[0] = 0;
256     foreach (i; 1 .. x.length)
257     {
258         const m_i = T(1) / i;
259         x[i] = 0;
260         w[i] = 1 / (4 - m_i ^^ 2);
261     }
262     return gaussQuadratureImpl!T(x, w, work, mu0, true);
263 }
264 
265 ///
266 unittest
267 {
268     import mir.math.common;
269     import mir.ndslice.allocation;
270 
271     auto n = 5;
272     auto x = new double[n];
273     auto w = new double[n];
274     auto work = new double[(n + 1) ^^ 2];
275 
276     gaussLegendreQuadrature(x, w, work);
277 
278     static immutable xc =
279        [-0.90617985,
280         -0.53846931,
281          0.        ,
282          0.53846931,
283          0.90617985];
284 
285     static immutable wc =
286        [0.23692689,
287         0.47862867,
288         0.56888889,
289         0.47862867,
290         0.23692689];
291 
292     foreach (i; 0 .. n)
293     {
294         assert(x[i].approxEqual(xc[i]));
295         assert(w[i].approxEqual(wc[i]));
296     }
297 }
298 
299 private size_t gaussQuadratureImpl(T)(
300     scope T[] alpha_x,
301     scope T[] beta_w,
302     scope T[] work,
303     double mu0,
304     bool symmetrize = false) @nogc
305 in {
306     assert(alpha_x.length == beta_w.length);
307     if (alpha_x.length)
308         assert(work.length >= (alpha_x.length + 1) ^^ 2);
309     foreach (ref b; beta_w[1 .. $])
310         assert (T.infinity > b && b > 0);
311 }
312 do {
313     pragma(inline, false);
314     auto n = alpha_x.length;
315     if (n == 0)
316         return n;
317     foreach (ref b; beta_w[1 .. n])
318         b = b.sqrt;
319     auto nq = n ^^ 2;
320     import mir.ndslice.slice: sliced;
321     import mir.ndslice.topology: canonical;
322     import mir.lapack: steqr;
323     auto z = work[0 .. nq].sliced(n, n);
324     auto info = steqr('I', alpha_x.sliced, beta_w[1 .. $].sliced, z.canonical, work[nq .. $].sliced);
325     foreach (i; 0 .. n)
326         beta_w[i] = z[i, 0] ^^ 2 * mu0;
327     if (symmetrize)
328     {
329         auto h = n / 2;
330         alias x = alpha_x;
331         alias w = beta_w;
332         foreach (i; 0 .. h)
333         {
334             x[i] = -(x[n - (i + 1)] = T(0.5) * (x[n - (i + 1)] - x[i]));
335             w[i] = +(w[n - (i + 1)] = T(0.5) * (w[n - (i + 1)] + w[i]));
336         }
337         if (n % 2)
338         {
339             x[h] = 0;
340         }
341     }
342     return info;
343 }