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 }