github.com/gopherd/gonum@v0.0.4/integrate/quad/hermite.go (about) 1 // Copyright ©2016 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package quad 6 7 import ( 8 "math" 9 10 "github.com/gopherd/gonum/floats" 11 "github.com/gopherd/gonum/mathext" 12 ) 13 14 // Hermite generates sample locations and weights for performing quadrature with 15 // a squared-exponential weight 16 // int_-inf^inf e^(-x^2) f(x) dx . 17 type Hermite struct{} 18 19 func (h Hermite) FixedLocations(x, weight []float64, min, max float64) { 20 // TODO(btracey): Implement the case where x > 20, x < 200 so that we don't 21 // need to store all of that data. 22 23 // Algorithm adapted from Chebfun http://www.chebfun.org/. 24 // 25 // References: 26 // Algorithm: 27 // G. H. Golub and J. A. Welsch, "Calculation of Gauss quadrature rules", 28 // Math. Comp. 23:221-230, 1969. 29 // A. Glaser, X. Liu and V. Rokhlin, "A fast algorithm for the 30 // calculation of the roots of special functions", SIAM Journal 31 // on Scientific Computing", 29(4):1420-1438:, 2007. 32 // A. Townsend, T. Trogdon, and S.Olver, Fast computation of Gauss quadrature 33 // nodes and weights on the whole real line, IMA J. Numer. Anal., 36: 337–358, 34 // 2016. http://arxiv.org/abs/1410.5286 35 36 if len(x) != len(weight) { 37 panic("hermite: slice length mismatch") 38 } 39 if min >= max { 40 panic("hermite: min >= max") 41 } 42 if !math.IsInf(min, -1) || !math.IsInf(max, 1) { 43 panic("hermite: non-infinite bound") 44 } 45 h.locations(x, weight) 46 } 47 48 func (h Hermite) locations(x, weights []float64) { 49 n := len(x) 50 switch { 51 case 0 < n && n <= 200: 52 copy(x, xCacheHermite[n-1]) 53 copy(weights, wCacheHermite[n-1]) 54 case n > 200: 55 h.locationsAsy(x, weights) 56 } 57 } 58 59 // Algorithm adapted from Chebfun http://www.chebfun.org/. Specific code 60 // https://github.com/chebfun/chebfun/blob/development/hermpts.m. 61 62 // Original Copyright Notice: 63 64 /* 65 Copyright (c) 2015, The Chancellor, Masters and Scholars of the University 66 of Oxford, and the Chebfun Developers. All rights reserved. 67 68 Redistribution and use in source and binary forms, with or without 69 modification, are permitted provided that the following conditions are met: 70 * Redistributions of source code must retain the above copyright 71 notice, this list of conditions and the following disclaimer. 72 * Redistributions in binary form must reproduce the above copyright 73 notice, this list of conditions and the following disclaimer in the 74 documentation and/or other materials provided with the distribution. 75 * Neither the name of the University of Oxford nor the names of its 76 contributors may be used to endorse or promote products derived from 77 this software without specific prior written permission. 78 79 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 80 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 81 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 82 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 83 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 84 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 85 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 86 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 87 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 88 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 89 */ 90 91 // locationAsy returns the node locations and weights of a Hermite quadrature rule 92 // with len(x) points. 93 func (h Hermite) locationsAsy(x, w []float64) { 94 // A. Townsend, T. Trogdon, and S.Olver, Fast computation of Gauss quadrature 95 // nodes and weights the whole real line, IMA J. Numer. Anal., 96 // 36: 337–358, 2016. http://arxiv.org/abs/1410.5286 97 98 // Find the positive locations and weights. 99 n := len(x) 100 l := n / 2 101 xa := x[l:] 102 wa := w[l:] 103 for i := range xa { 104 xa[i], wa[i] = h.locationsAsy0(i, n) 105 } 106 // Flip around zero -- copy the negative x locations with the corresponding 107 // weights. 108 if n%2 == 0 { 109 l-- 110 } 111 for i, v := range xa { 112 x[l-i] = -v 113 } 114 for i, v := range wa { 115 w[l-i] = v 116 } 117 sumW := floats.Sum(w) 118 c := math.SqrtPi / sumW 119 floats.Scale(c, w) 120 } 121 122 // locationsAsy0 returns the location and weight for location i in an n-point 123 // quadrature rule. The rule is symmetric, so i should be <= n/2 + n%2. 124 func (h Hermite) locationsAsy0(i, n int) (x, w float64) { 125 const convTol = 1e-16 126 const convIter = 20 127 theta0 := h.hermiteInitialGuess(i, n) 128 t0 := theta0 / math.Sqrt(2*float64(n)+1) 129 theta0 = math.Acos(t0) 130 sqrt2np1 := math.Sqrt(2*float64(n) + 1) 131 var vali, dvali float64 132 for k := 0; k < convIter; k++ { 133 vali, dvali = h.hermpolyAsyAiry(i, n, theta0) 134 dt := -vali / (math.Sqrt2 * sqrt2np1 * dvali * math.Sin(theta0)) 135 theta0 -= dt 136 if math.Abs(theta0) < convTol { 137 break 138 } 139 } 140 x = sqrt2np1 * math.Cos(theta0) 141 ders := x*vali + math.Sqrt2*dvali 142 w = math.Exp(-x*x) / (ders * ders) 143 return x, w 144 } 145 146 // hermpolyAsyAiry evaluates the Hermite polynomials using the Airy asymptotic 147 // formula in theta-space. 148 func (h Hermite) hermpolyAsyAiry(i, n int, t float64) (valVec, dvalVec float64) { 149 musq := 2*float64(n) + 1 150 cosT := math.Cos(t) 151 sinT := math.Sin(t) 152 sin2T := 2 * cosT * sinT 153 eta := 0.5*t - 0.25*sin2T 154 chi := -math.Pow(3*eta/2, 2.0/3) 155 phi := math.Pow(-chi/(sinT*sinT), 1.0/4) 156 cnst := 2 * math.SqrtPi * math.Pow(musq, 1.0/6) * phi 157 airy0 := real(mathext.AiryAi(complex(math.Pow(musq, 2.0/3)*chi, 0))) 158 airy1 := real(mathext.AiryAiDeriv(complex(math.Pow(musq, 2.0/3)*chi, 0))) 159 160 // Terms in 12.10.43: 161 const ( 162 a1 = 15.0 / 144 163 b1 = -7.0 / 5 * a1 164 a2 = 5.0 * 7 * 9 * 11.0 / 2.0 / 144.0 / 144.0 165 b2 = -13.0 / 11 * a2 166 a3 = 7.0 * 9 * 11 * 13 * 15 * 17 / 6.0 / 144.0 / 144.0 / 144.0 167 b3 = -19.0 / 17 * a3 168 ) 169 170 // Pre-compute terms. 171 cos2T := cosT * cosT 172 cos3T := cos2T * cosT 173 cos4T := cos3T * cosT 174 cos5T := cos4T * cosT 175 cos7T := cos5T * cos2T 176 cos9T := cos7T * cos2T 177 178 chi2 := chi * chi 179 chi3 := chi2 * chi 180 chi4 := chi3 * chi 181 chi5 := chi4 * chi 182 183 phi6 := math.Pow(phi, 6) 184 phi12 := phi6 * phi6 185 phi18 := phi12 * phi6 186 187 // u polynomials in 12.10.9. 188 u1 := (cos3T - 6*cosT) / 24.0 189 u2 := (-9*cos4T + 249*cos2T + 145) / 1152.0 190 u3 := (-4042*cos9T + 18189*cos7T - 28287*cos5T - 151995*cos3T - 259290*cosT) / 414720.0 191 192 val := airy0 193 B0 := -(phi6*u1 + a1) / chi2 194 val += B0 * airy1 / math.Pow(musq, 4.0/3) 195 A1 := (phi12*u2 + b1*phi6*u1 + b2) / chi3 196 val += A1 * airy0 / (musq * musq) 197 B1 := -(phi18*u3 + a1*phi12*u2 + a2*phi6*u1 + a3) / chi5 198 val += B1 * airy1 / math.Pow(musq, 4.0/3+2) 199 val *= cnst 200 201 // Derivative. 202 eta = 0.5*t - 0.25*sin2T 203 chi = -math.Pow(3*eta/2, 2.0/3) 204 phi = math.Pow(-chi/(sinT*sinT), 1.0/4) 205 cnst = math.Sqrt2 * math.SqrtPi * math.Pow(musq, 1.0/3) / phi 206 207 // v polynomials in 12.10.10. 208 v1 := (cos3T + 6*cosT) / 24 209 v2 := (15*cos4T - 327*cos2T - 143) / 1152 210 v3 := (259290*cosT + 238425*cos3T - 36387*cos5T + 18189*cos7T - 4042*cos9T) / 414720 211 212 C0 := -(phi6*v1 + b1) / chi 213 dval := C0 * airy0 / math.Pow(musq, 2.0/3) 214 dval += airy1 215 C1 := -(phi18*v3 + b1*phi12*v2 + b2*phi6*v1 + b3) / chi4 216 dval += C1 * airy0 / math.Pow(musq, 2.0/3+2) 217 D1 := (phi12*v2 + a1*phi6*v1 + a2) / chi3 218 dval += D1 * airy1 / (musq * musq) 219 dval *= cnst 220 return val, dval 221 } 222 223 // hermiteInitialGuess returns the initial guess for node i in an n-point Hermite 224 // quadrature rule. The rule is symmetric, so i should be <= n/2 + n%2. 225 func (h Hermite) hermiteInitialGuess(i, n int) float64 { 226 // There are two different formulas for the initial guesses of the hermite 227 // quadrature locations. The first uses the Gatteschi formula and is good 228 // near x = sqrt(n+0.5) 229 // [1] L. Gatteschi, Asymptotics and bounds for the zeros of Laguerre 230 // polynomials: a survey, J. Comput. Appl. Math., 144 (2002), pp. 7-27. 231 // The second is the Tricomi initial guesses, good near x = 0. This is 232 // equation 2.1 in [1] and is originally from 233 // [2] F. G. Tricomi, Sugli zeri delle funzioni di cui si conosce una 234 // rappresentazione asintotica, Ann. Mat. Pura Appl. 26 (1947), pp. 283-300. 235 236 // If the number of points is odd, there is a quadrature point at 1, which 237 // has an initial guess of 0. 238 if n%2 == 1 { 239 if i == 0 { 240 return 0 241 } 242 i-- 243 } 244 245 m := n / 2 246 a := -0.5 247 if n%2 == 1 { 248 a = 0.5 249 } 250 nu := 4*float64(m) + 2*a + 2 251 252 // Find the split between Gatteschi guesses and Tricomi guesses. 253 p := 0.4985 + math.SmallestNonzeroFloat64 254 pidx := int(math.Floor(p * float64(n))) 255 256 // Use the Tricomi initial guesses in the first half where x is nearer to zero. 257 // Note: zeros of besselj(+/-.5,x) are integer and half-integer multiples of pi. 258 if i < pidx { 259 rhs := math.Pi * (4*float64(m) - 4*(float64(i)+1) + 3) / nu 260 tnk := math.Pi / 2 261 for k := 0; k < 7; k++ { 262 val := tnk - math.Sin(tnk) - rhs 263 dval := 1 - math.Cos(tnk) 264 dTnk := val / dval 265 tnk -= dTnk 266 if math.Abs(dTnk) < 1e-14 { 267 break 268 } 269 } 270 vc := math.Cos(tnk / 2) 271 t := vc * vc 272 return math.Sqrt(nu*t - (5.0/(4.0*(1-t)*(1-t))-1.0/(1-t)-1+3*a*a)/3/nu) 273 } 274 275 // Use Gatteschi guesses in the second half where x is nearer to sqrt(n+0.5) 276 i = m - (i + 1) 277 var ar float64 278 if i < len(airyRtsExact) { 279 ar = airyRtsExact[i] 280 } else { 281 t := 3.0 / 8 * math.Pi * (4*(float64(i)+1) - 1) 282 ar = math.Pow(t, 2.0/3) * (1 + 283 5.0/48*math.Pow(t, -2) - 284 5.0/36*math.Pow(t, -4) + 285 77125.0/82944*math.Pow(t, -6) - 286 108056875.0/6967296*math.Pow(t, -8) + 287 162375596875.0/334430208*math.Pow(t, -10)) 288 } 289 r := nu + math.Pow(2, 2.0/3)*ar*math.Pow(nu, 1.0/3) + 290 0.2*math.Pow(2, 4.0/3)*ar*ar*math.Pow(nu, -1.0/3) + 291 (11.0/35-a*a-12.0/175*ar*ar*ar)/nu + 292 (16.0/1575*ar+92.0/7875*math.Pow(ar, 4))*math.Pow(2, 2.0/3)*math.Pow(nu, -5.0/3) - 293 (15152.0/3031875*math.Pow(ar, 5)+1088.0/121275*ar*ar)*math.Pow(2, 1.0/3)*math.Pow(nu, -7.0/3) 294 if r < 0 { 295 ar = 0 296 } else { 297 ar = math.Sqrt(r) 298 } 299 return ar 300 } 301 302 // airyRtsExact are the first airy roots. 303 var airyRtsExact = []float64{ 304 -2.338107410459762, 305 -4.087949444130970, 306 -5.520559828095555, 307 -6.786708090071765, 308 -7.944133587120863, 309 -9.022650853340979, 310 -10.040174341558084, 311 -11.008524303733260, 312 -11.936015563236262, 313 -12.828776752865757, 314 }