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  }