Source code for recursivenodes.polynomials

'''Polynomials used in ``recursivenodes``.'''

import numpy as np
from math import lgamma
from recursivenodes.utils import multiindex_up_to, npolys


def _jacobi_recurrence(n, alpha=0., beta=0.):
    '''Three term recurrence coefficients for Jacobi polynomials,

    $$ P_n = (a_n x + b_n) P_{n-1} - c_n P_{n-2}. $$

    returns (a_n, b_n, c_n)
    '''
    n = np.array(n, dtype='int')
    a = np.zeros(n.shape)
    b = np.zeros(n.shape)
    c = np.zeros(n.shape)
    a[n == 1] = (alpha+beta+2)/2
    b[n == 1] = (alpha-beta)/2
    N = n[n > 1]
    a[n > 1] = (2*N+alpha+beta)*(2*N+alpha+beta-1) / (2*N*(N+alpha+beta))
    b[n > 1] = ((alpha*alpha-beta*beta)*(2*N+alpha+beta-1)
                / (2*N*(N+alpha+beta)*(2*N+alpha+beta-2)))
    c[n > 1] = (2*(N+alpha-1)*(N+beta-1)*(2*N+alpha+beta)
                / (2*N*(N+alpha+beta)*(2*N+alpha+beta-2)))
    return (a, b, c)


def _eval_jacobi(n, alpha, beta, x, out):
    '''reimplementation of scipy.special.eval_jacobi (only for integer n)'''
    x = np.array(x)
    if out is None:
        out = np.ones(x.shape)
    else:
        out[:] = 1.
    n = int(n)
    if n == 0:
        return out
    ns = list(range(1, n+1))
    a, b, c = _jacobi_recurrence(ns, alpha, beta)
    pm1 = np.zeros(out.shape)
    pm2 = np.ndarray(out.shape)
    for i in range(n):
        pm2[...] = pm1[...]
        pm1[...] = out[...]
        out[...] = (a[i]*x+b[i])*pm1 - c[i]*pm2
    return out


try:
    from scipy.special import eval_jacobi
except ImportError:
    eval_jacobi = _eval_jacobi


[docs]def jacobi(n, x, a=0., b=0., out=None): '''Evaluation of a Jacobi polynomial. Args: n (int): The degree of the polynomial. x (ndarray): Points at which to evaluate the polynomial. a (float, optional): Left exponent of weight function. b (float, optional): Right exponent of weight function. out (ndarray, optional): Output placed here if given, same shape as ``x``. Returns: ndarray: same shape as ``x``, values of the `n`-th Jacobi polynomials with respect to the weight function `(1+x)^a(1-x)^b` at the points in ``x``. ''' return eval_jacobi(n, a, b, x, out)
[docs]def jacobider(n, x, a=0., b=0., k=1, out=None): '''Evaluation of a the `k`-th derivative of a Jacobi polynomial. Args: n (int): The degree of the polynomial. x (ndarray): Points at which to evaluate the polynomial. a (float, optional): Left exponent of weight function. b (float, optional): Right exponent of weight function. out (ndarray, optional): Output placed here if given, same shape as ``x``. Returns: ndarray: same shape as ``x``, values of the `k`-th derivative of the `n`-th Jacobi polynomials with respect to the weight function `(1+x)^a(1-x)^b` at the points in ``x``. ''' if k > n: if out: out[:] = 0. return out else: return np.zeros(np.shape(x)) return jacobi(n-k, x, a+k, b+k) * np.exp(lgamma(a+b+n+1+k) - lgamma(a+b+n+1)) / 2**k
[docs]def jacobinorm2(n, a=0., b=0.): '''The square of the weighted `L_2` norm of a Jacobi polynomial. Args: n (int): The degree of the polynomial. x (ndarray): Points at which to evaluate the polynomial. a (float, optional): Left exponent of weight function. b (float, optional): Right exponent of weight function. Returns: float: `\\int_{-1}^1 (1+x)^a (1-x)^b P^{(a,b)}_n(x)^2\\ dx`. ''' if n == 0: return 2.**(a+b+1) * np.exp(lgamma(a + 1) + lgamma(b + 1) - lgamma(a + b + 2)) else: return (2.**(a+b+1) / (2*n + a + b + 1) * np.exp(lgamma(n + a + 1) + lgamma(n + b + 1) - lgamma(n + a + b + 1) - lgamma(n + 1)))
[docs]def proriolkoornwinderdubiner(d, i, x, out=None): '''Evaluation of a Proriol-Koornwinder-Dubiner (PKD) polynomial. Args: d (int): The spatial dimension. i (tuple(int)): Multi-index of length `d`, the degree of the leading monomial of the PKD polynomial in each spatial coordinate. x (ndarray): Shape (`N`, `d`), points at which to evaluate the PDK polynomial. out (ndarray, optional): Shape (`N`,) array to hold output. Returns: ndarray: Shape (`N`,), evaluation of the PKD polynomial with leading monomial degrees `i` at the points `x`. The PKD polynomials are orthonormal on the biunit simplex. References: :cite:`Pror57,KaMc64,Koor75,Dubi91` ''' if (d == 1): return jacobi(i[0], x[:, 0], out=out) / jacobinorm2(i[0])**0.5 else: isum = sum(i[0:d-1]) factor = (1. - x[:, d-1]) tol = 1.e-10 nonzero = np.abs(factor) > tol if out is None: px = np.ndarray(x[:, 0:(d-1)].shape) else: px = out px[nonzero, :] = ((x[nonzero, 0:(d-1)] + 1.) * 2. / factor[nonzero, np.newaxis] - 1.) px[~nonzero, :] = -1. pi = proriolkoornwinderdubiner(d-1, i[0:d-1], px) pi *= jacobi(i[-1], x[:, d-1], 2*isum + d - 1, 0.) pi /= jacobinorm2(i[-1], 2*isum+d-1, 0.)**0.5 pi *= factor**isum pi *= 2.**((d-1)/2) return pi
[docs]def proriolkoornwinderdubinergrad(d, i, x, out=None, both=False): '''Evaluation of the gradient of a Proriol-Koornwinder-Dubiner (PKD) polynomial. Args: d (int): The spatial dimension. i (tuple(int)): Multi-index of length `d`, the degree of the leading monomial of the PKD polynomial in each spatial coordinate. x (ndarray): Shape (`N`, `d`), points at which to evaluate the PDK polynomial. out (ndarray, optional): Shape (`N`, `d`) array to hold output. both: (bool, optional): If ``True``, return both the function value and gradient. Returns: If ``both=False``, an ndarray with shape (`N`, `d`) containing the gradient of the PKD polynomial with leading monomial degrees `i` at the points `x`. If ``both=True``, a tuple containing two ndarrays, one for the functional value and one for its gradient, in that order. ''' if (d == 1): jnorm = jacobinorm2(i[0])**0.5 if both: pkd = jacobi(i[0], x[:, 0]) / jnorm out = jacobider(i[0], x[:, 0]).reshape(x.shape) out /= jnorm if both: return (pkd, out) return out else: isum = sum(i[0:d-1]) factor = (1. - x[:, d-1]) tol = 1.e-10 nonzero = np.abs(factor) > tol px = np.ndarray(x[:, 0:(d-1)].shape) px[nonzero, :] = ((x[nonzero, 0:(d-1)] + 1.) * 2. / factor[nonzero, np.newaxis] - 1.) px[~nonzero, :] = -1. pxgradfisum = np.zeros(px.shape + (d,)) for j in range(d-1): pxgradfisum[nonzero, j, j] = 2. * factor[nonzero]**(isum - 1) pxgradfisum[nonzero, j, d-1] = ((x[nonzero, j] + 1.) * 2. * factor[nonzero]**(isum-2)) if isum > 0: pxgradfisum[~nonzero, j, j] = 2. * factor[~nonzero]**(isum - 1) # otherwise, pigrad will be zero if isum > 1: pxgradfisum[~nonzero, j, d-1] = ((x[~nonzero, j] + 1.) * 2. * factor[~nonzero]**(isum-2)) # otherwise, pigrad * pzgrad will be independent of z pi, pigrad = proriolkoornwinderdubinergrad(d-1, i[0:d-1], px, both=True) pz = jacobi(i[-1], x[:, d-1], 2*isum + d - 1, 0.) jnorm = jacobinorm2(i[-1], 2*isum+d-1, 0.)**0.5 pz /= jnorm pzgrad = jacobider(i[-1], x[:, d-1], 2*isum + d - 1, 0.) pzgrad /= jnorm fisum = factor**isum if not (out is None): pkdgrad = out pkdgrad[:] = 0. else: pkdgrad = np.zeros(x.shape) if both: pkd = pi * pz * fisum * 2.**((d-1)/2) pkdgrad[:, :] = (np.einsum('ij,ijk->ik', pigrad, pxgradfisum).reshape(x.shape) * pz[:, np.newaxis]) pkdgrad[:, d-1] += pi * pzgrad * fisum if (isum != 0): pkdgrad[:, d-1] -= isum * pi * pz * factor**(isum-1) pkdgrad *= 2.**((d-1)/2) if both: return (pkd, pkdgrad) return pkdgrad
[docs]def proriolkoornwinderdubinerhess(d, i, x): '''Evaluation of the gradient of a Proriol-Koornwinder-Dubiner (PKD) polynomial. Args: d (int): The spatial dimension. i (tuple(int)): Multi-index of length `d`, the degree of the leading monomial of the PKD polynomial in each spatial coordinate. x (ndarray): Shape (`N`, `d`), points at which to evaluate the PDK polynomial. Returns: ndarray: Shape (`N`, `d`, `d`) containing the Hessian of the PKD polynomial with leading monomial degrees `i` at the points `x`. ''' if (d == 1): jnorm = jacobinorm2(i[0])**0.5 out = jacobider(i[0], x[:, 0], k=2).reshape(x.shape + (1,)) out /= jnorm return out else: isum = sum(i[0:d-1]) factor = (1. - x[:, d-1]) tol = 1.e-10 nonzero = np.abs(factor) > tol px = np.ndarray(x[:, 0:(d-1)].shape) px[nonzero, :] = ((x[nonzero, 0:(d-1)] + 1.) * 2. / factor[nonzero, np.newaxis] - 1.) px[~nonzero, :] = -1. pxgradf = np.zeros(px.shape + (d,)) pxgradfisum = np.zeros(px.shape + (d,)) pxgradfisumm1 = np.zeros(px.shape + (d,)) pxhessfisum = np.zeros(px.shape + (d, d)) for j in range(d-1): pxgradf[:, j, j] = 2. pxgradf[nonzero, j, d-1] = ((x[nonzero, j] + 1.) * 2. / factor[nonzero]) pxgradfisum[nonzero, j, j] = 2. * factor[nonzero]**(isum - 1) pxgradfisum[nonzero, j, d-1] = ((x[nonzero, j] + 1.) * 2. * factor[nonzero]**(isum-2)) if isum > 0: pxgradfisum[~nonzero, j, j] = 2. * factor[~nonzero]**(isum - 1) # otherwise, pigrad will be zero if isum > 1: pxgradfisum[~nonzero, j, d-1] = ((x[~nonzero, j] + 1.) * 2. * factor[~nonzero]**(isum-2)) # otherwise, pigrad * pzgrad will be independent of z pxgradfisumm1[nonzero, j, j] = 2. * factor[nonzero]**(isum - 2) pxgradfisumm1[nonzero, j, d-1] = ((x[nonzero, j] + 1.) * 2. * factor[nonzero]**(isum-3)) if isum > 1: pxgradfisumm1[~nonzero, j, j] = 2. * factor[~nonzero]**(isum - 2) # otherwise, pigrad will be zero if isum > 2: pxgradfisumm1[~nonzero, j, d-1] = ((x[~nonzero, j] + 1.) * 2. * factor[~nonzero]**(isum-3)) # otherwise, pigrad * pzgrad will be independent of z pxhessfisum[nonzero, j, d-1, j] = 2. * factor[nonzero]**(isum-2) pxhessfisum[nonzero, j, j, d-1] = 2. * factor[nonzero]**(isum-2) pxhessfisum[nonzero, j, d-1, d-1] = ((x[nonzero, j] + 1.) * 4. * factor[nonzero]**(isum-3)) if isum > 1: pxhessfisum[~nonzero, j, d-1, j] = 2. * factor[~nonzero]**(isum-2) pxhessfisum[~nonzero, j, j, d-1] = 2. * factor[~nonzero]**(isum-2) if isum > 2: pxhessfisum[~nonzero, j, d-1, d-1] = ((x[~nonzero, j] + 1.) * 4. * factor[~nonzero]**(isum-3)) pi, pigrad = proriolkoornwinderdubinergrad(d-1, i[0:d-1], px, both=True) pihess = proriolkoornwinderdubinerhess(d-1, i[0:d-1], px) pz = jacobi(i[-1], x[:, d-1], 2*isum + d - 1, 0.) jnorm = jacobinorm2(i[-1], 2*isum+d-1, 0.)**0.5 pz /= jnorm pzgrad = jacobider(i[-1], x[:, d-1], 2*isum + d - 1, 0.) pzgrad /= jnorm pzhess = jacobider(i[-1], x[:, d-1], 2*isum + d - 1, 0., k=2) pzhess /= jnorm fisum = factor**isum pkdhess = np.zeros(x.shape + (d,)) pizgradfisum = np.einsum('ij,ijk->ik', pigrad, pxgradfisum).reshape(x.shape) pizgradfisumm1 = np.einsum('ij,ijk->ik', pigrad, pxgradfisumm1).reshape(x.shape) pkdhess += (np.einsum('ijl,ijk,ilm->ikm', pihess, pxgradf, pxgradfisumm1).reshape(x.shape + (d,)) * pz[:, np.newaxis, np.newaxis]) pkdhess += (np.einsum('ij,ijkl->ikl', pigrad, pxhessfisum).reshape(x.shape + (d,)) * pz[..., np.newaxis, np.newaxis]) pkdhess[:, d-1, d-1] += pi * pzhess * fisum if (isum > 1): pkdhess[:, d-1, d-1] += (isum-1)*isum * pi * pz * factor**(isum-2) temp = pizgradfisum * pzgrad[..., np.newaxis] pkdhess[:, :, d-1] += temp pkdhess[:, d-1, :] += temp if (isum > 0): temp = pizgradfisumm1 * (pz * isum)[..., np.newaxis] pkdhess[:, :, d-1] -= temp pkdhess[:, d-1, :] -= temp if (isum > 0): pkdhess[:, d-1, d-1] -= 2. * pi * pzgrad * isum * factor**(isum-1) pkdhess *= 2.**((d-1)/2) return pkdhess
[docs]def proriolkoornwinderdubinervandermonde(d, n, x, out=None, C=None): '''Evaluation of the Vandermonde matrix of the Proriol-Koornwinder-Dubiner (PKD) polynomials. Args: d (int): The spatial dimension. n (int): The maximum degree of polynomials to include in the Vandermonde matrix. x (ndarray): Shape (`N`, `d`), points at which to evaluate the PDK polynomial Vandermonde matrix. out (ndarray, optional): Shape (`N`, `\\binom{n+d}{d}`) array to hold output (or, if ``C`` is given, shape (`N`, `M`), where `M` is the number of columns of `C`). C (ndarray, optional): Shape (`\\binom{n+d}{d}`, `M`) matrix to multiply the PDK Vandermonde matrix by on the right. Returns: ndarray: Shape (`N`, `\\binom{n+d}{d}`), evaluation of the PKD polynomials with leading monomial degree at most `n` at the points `x`. The columns will index the PKD polynomials in lexicographic degree of their leading monomial exponents. Or, if `C` is given, shape (`N`, `M`), the product of the Vandermonde matrix with `C`. ''' N = npolys(d, n) if out: V = out else: if C is None: V = np.ndarray(x.shape[0:-1]+(N,)) else: V = np.ndarray(x.shape[0:-1]+(C.shape[1],)) if not (C is None): V[:] = 0. for (k, i) in enumerate(multiindex_up_to(d, n)): if C is None: V[:, k] = proriolkoornwinderdubiner(d, i, x) else: V[:, :] += np.einsum('i,j->ij', proriolkoornwinderdubiner(d, i, x), C[k, :]) return V
[docs]def proriolkoornwinderdubinervandermondegrad(d, n, x, out=None, C=None, work=None, both=False): '''Evaluation of the gradient of the Vandermonde matrix of the Proriol-Koornwinder-Dubiner (PKD) polynomials. Args: d (int): The spatial dimension. n (int): The maximum degree of polynomials to include in the Vandermonde matrix. x (ndarray): Shape (`N`, `d`), points at which to evaluate the PDK polynomial Vandermonde matrix. out (ndarray, optional): Shape (`N`, `\\binom{n+d}{d}`, `d`) array to hold output (or, if ``C`` is given, shape (`N`, `M`, `d`), where `M` is the number of columns of `C`). C (ndarray, optional): Shape (`\\binom{n+d}{d}`, `M`) matrix to multiply the PDK Vandermonde matrix by on the right. Returns: ndarray: Shape (`N`, `\\binom{n+d}{d}`, `d`), evaluation of the gradient of the PKD Vandermonde matrix at the points `x`. Or, if `C` is given, shape (`N`, `M`, `d`), the gradient of the product of the Vandermonde matrix with `C`. ''' N = npolys(d, n) if out: Vg = out else: if C is None: Vg = np.ndarray(x.shape[0:-1]+(N, d)) else: Vg = np.ndarray(x.shape[0:-1]+(C.shape[1], d)) if both: if C is None: V = np.ndarray(x.shape[0:-1]+(N,)) else: V = np.ndarray(x.shape[0:-1]+(C.shape[1],)) if both: V[:] = 0. Vg[:] = 0. if work is None: work = np.ndarray(x.shape[0:-1] + (d,)) for (k, i) in enumerate(multiindex_up_to(d, n)): if both: v, work = proriolkoornwinderdubinergrad(d, i, x, out=work, both=True) else: work = proriolkoornwinderdubinergrad(d, i, x, out=work) if C is None: if both: V[:, k] = v Vg[:, k, 0:d] = work else: Vg[:, k, 0:d] = work else: if both: V[:, :] += np.einsum('i,j->ij', v, C[k, :]) Vg[:, :, :] += np.einsum('ij,k->ikj', work, C[k, :]) else: Vg[:, :, :] += np.einsum('ij,k->ikj', work, C[k, :]) if both: return (V, Vg) return Vg
[docs]def proriolkoornwinderdubinervandermondehess(d, n, x, C=None): '''Evaluation of the Hessian of the Vandermonde matrix of the Proriol-Koornwinder-Dubiner (PKD) polynomials. Args: d (int): The spatial dimension. n (int): The maximum degree of polynomials to include in the Vandermonde matrix. x (ndarray): Shape (`N`, `d`), points at which to evaluate the PDK polynomial Vandermonde matrix. C (ndarray, optional): Shape (`\\binom{n+d}{d}`, `M`) matrix to multiply the PDK Vandermonde matrix by on the right. Returns: ndarray: Shape (`N`, `\\binom{n+d}{d}`, `d`, `d`), evaluation of the Hessian of the PKD Vandermonde matrix at the points `x`. Or, if `C` is given, shape (`N`, `M`, `d`), the Hessian of the product of the Vandermonde matrix with `C`. ''' N = npolys(d, n) if C is None: Vh = np.ndarray(x.shape[0:-1]+(N, d, d)) else: Vh = np.ndarray(x.shape[0:-1]+(C.shape[1], d, d)) Vh[:] = 0. for (k, i) in enumerate(multiindex_up_to(d, n)): work = proriolkoornwinderdubinerhess(d, i, x) if C is None: Vh[:, k, :, :] = work else: Vh[:, :, :, :] += np.einsum('ijk,l->iljk', work, C[k, :]) return Vh