def gaussian_latitudes(n): """Construct latitudes and latitude bounds for a Gaussian grid. Args: * n: The Gaussian grid number (half the number of latitudes in the grid. Returns: A 2-tuple where the first element is a length `n` array of latitudes (in degrees) and the second element is an `(n, 2)` array of bounds. """ if abs(int(n)) != n: raise ValueError('n must be a non-negative integer') nlat = 2 * n # Create the coefficients of the Legendre polynomial and construct the # companion matrix: cs = np.array([0] * nlat + [1], dtype=np.int) cm = legcompanion(cs) # Compute the eigenvalues of the companion matrix (the roots of the # Legendre polynomial) taking advantage of the fact that the matrix is # symmetric: roots = la.eigvalsh(cm) roots.sort() # Improve the roots by one application of Newton's method, using the # solved root as the initial guess: fx = legval(roots, cs) fpx = legval(roots, legder(cs)) roots -= fx / fpx # The roots should exhibit symmetry, but with a sign change, so make sure # this is the case: roots = (roots - roots[::-1]) / 2. # Compute the Gaussian weights for each interval: fm = legval(roots, cs[1:]) fm /= np.abs(fm).max() fpx /= np.abs(fpx).max() weights = 1. / (fm * fpx) # Weights should be symmetric and sum to two (unit weighting over the # interval [-1, 1]): weights = (weights + weights[::-1]) / 2. weights *= 2. / weights.sum() # Calculate the bounds from the weights, still on the interval [-1, 1]: bounds1d = np.empty([nlat + 1]) bounds1d[0] = -1 bounds1d[1:-1] = -1 + weights[:-1].cumsum() bounds1d[-1] = 1 # Convert the bounds to degrees of latitude on [-90, 90]: bounds1d = np.rad2deg(np.arcsin(bounds1d)) bounds2d = np.empty([nlat, 2]) bounds2d[:, 0] = bounds1d[:-1] bounds2d[:, 1] = bounds1d[1:] # Convert the roots from the interval [-1, 1] to latitude values on the # interval [-90, 90] degrees: latitudes = np.rad2deg(np.arcsin(roots)) return latitudes, bounds2d
def gglat(nlat, nnewton=5): """ Compute and return latitudes on a Gaussian grid. Based on https://gist.github.com/ajdawson/b64d24dfac618b91974f. Parameters ---------- nlat: int Number of latitude points Keyword arguments ----------------- nnewton: int, default 5 Number of Newton iterations used to improve roots Returns ------- array-like Gaussian grid latitudes """ assert (nlat > 0 and nlat % 2 == 0), 'nlat must be positive and even' # Generate companion matrix coef = np.array([0] * nlat + [1], dtype=np.int) com = legcompanion(coef) # Calculate roots roots = la.eigvalsh(com) roots.sort() for _ in range(nnewton): f = legval(roots, coef) df = legval(roots, legder(coef)) roots -= f / df # Ensure symmetry roots = (roots - roots[::-1]) / 2.0 return np.arcsin(roots) * 180.0 / np.pi
def test_linear_root(self): assert_(leg.legcompanion([1, 2])[0, 0] == -.5)
def test_dimensions(self): for i in range(1, 5): coef = [0]*i + [1] assert_(leg.legcompanion(coef).shape == (i, i))
def leggauss_improve(deg): """ Gauss-Legendre quadrature. Computes the sample points and weights for Gauss-Legendre quadrature. These sample points and weights will correctly integrate polynomials of degree :math:`2*deg - 1` or less over the interval :math:`[-1, 1]` with the weight function :math:`f(x) = 1`. Parameters ---------- deg : int Number of sample points and weights. It must be >= 1. Returns ------- x : ndarray 1-D ndarray containing the sample points. y : ndarray 1-D ndarray containing the weights. Notes ----- .. versionadded:: 1.7.0 The results have only been tested up to degree 100, higher degrees may be problematic. The weights are determined by using the fact that .. math:: w_k = c / (L'_n(x_k) * L_{n-1}(x_k)) where :math:`c` is a constant independent of :math:`k` and :math:`x_k` is the k'th root of :math:`L_n`, and then scaling the results to get the right value when integrating 1. """ ideg = int(deg) if ideg != deg or ideg < 1: raise ValueError("deg must be a non-negative integer") # first approximation of roots. We use the fact that the companion # matrix is symmetric in this case in order to obtain better zeros. c = np.array([0]*deg + [1]) m = legcompanion(c) x = la.eigvalsh(m) # # improve roots by one application of Newton dy = legval(x, c) df = legval(x, legder(c)) x -= dy/df # # improve roots again # dy = legval(x, c) # df = legval(x, legder(c)) # x -= dy/df # compute the weights. We scale the factor to avoid possible numerical # overflow. fm = legval(x, c[1:]) fm /= np.abs(fm).max() df /= np.abs(df).max() w = 1/(fm * df) # for Legendre we can also symmetrize w = (w + w[::-1])/2 x = (x - x[::-1])/2 # scale w to get the right value w *= 2. / w.sum() return x, w, dy/df