Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
 def test_linear_root(self):
     assert_(leg.legcompanion([1, 2])[0, 0] == -.5)
Example #5
0
 def test_dimensions(self):
     for i in range(1, 5):
         coef = [0]*i + [1]
         assert_(leg.legcompanion(coef).shape == (i, i))
Example #6
0
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