Problem 7.19

Problem 7.19#

The lagrange polynomials of degree \(n\) are described by their control points \(\xi_i\) , in this case GLL points, as

\[\begin{equation*} \ell_i^n (\xi) = \prod_{\substack{j=0\\j \neq i}}^{n} \frac{\xi - \xi_j}{\xi_i - \xi_j} \quad. \end{equation*}\]

Using the product rule the derivative is then

\[\begin{equation*} \ell'{}^n_i(\xi) = \sum_{\substack{j=0\\j \neq i}}^{n} \left[ \frac{1}{\xi_i - \xi_j} \prod_{\substack{m=0\\m \neq (i,j)}}^{n} \frac{\xi - \xi_m}{\xi_i - \xi_m} \right] \quad. \end{equation*}\]

An alternative formula for the derivative at the GLL points is given in Canuto et al 2006, Eqn. 2.3.28 in terms of Legendre polynomials of degree \(n\), \(P_n\):

\[\begin{equation*} \ell_i'(\xi_j) = \begin{cases} \frac{P_n(\xi_i)}{P_n(\xi_j)(\xi_i - \xi_j)} \:, & i \ne j \\ -\frac{n}{4}(n+1) \:, & i = j = 0 \\ \frac{n}{4}(n+1) \:, & i = j = n \\ 0 \:, & \text{otherwise} \end{cases} \end{equation*}\]

To evaluate the function at GLL points specifically, we will need a function to compute the GLL points for a specific \(n\) value. This function, copied from Problem 7.17 is in the cell below.

Hide code cell source
import scipy.special as ss 
import numpy as np 
from copy import copy
import matplotlib.pyplot as plt


def gll(N, Nsegs=100): 
    # This function is the solution to 7.17. 
    # It is not the most efficient way to compute GLL points,
    # but it works!
    nroots  = n + 1              # Number of roots
    roots   = np.zeros(nroots)   # Array to hold all the roots
    leg_n   = ss.legendre(n)    # P_n  
    leg_n_1 = ss.legendre(n-1)  # P_{n-1}

    def compute_functional(xi, n, lnm1, ln):
        return n * (lnm1(xi) - xi * ln(xi))
    
    # End points 
    roots[0]   = -1
    roots[-1]  =  1
    nrts_found =  2

    if nroots > 3:
        xi_end = 0.99999
        if n%2==0:
            xi_start = 0.00001
            nrts_found += 1
        else:
            xi_start = 0.0
        segments = np.linspace(xi_start, xi_end, Nsegs)
        fseg = compute_functional(segments, n, leg_n_1, leg_n)
        # Index in the 'roots' array to store the root
        idx = int(nroots / 2) + nroots%2 
        # Loop through each segment and test if root lies within
        for iseg in range(Nsegs-1):
            if fseg[iseg] == 0:
                # Root found exactly here
                error = 0
                nrts_found += 1
            elif fseg[iseg+1] == 0:
                # Root found exactly here
                error = 0
                nrts_found += 1
            elif fseg[iseg] * fseg[iseg+1] < 0:
                # There is a change in sign between the two values:
                # Start with the current edge xi positions of the 'segment'
                a = segments[iseg]
                b = segments[iseg+1]
                error = 999
                while np.abs(error) > 1e-9:
                    grad =  compute_functional(b, n, leg_n_1, leg_n) \
                          - compute_functional(a, n, leg_n_1, leg_n)
                    this_root = a + (b-a)/2
                    error    =  compute_functional(this_root, n, leg_n_1, leg_n)
                    if error !=0:
                        if grad*error > 0:
                            b = copy(this_root)
                        else:
                            a = copy(this_root)
                roots[idx] = this_root
                idx += 1
                nrts_found+=2
                
        for irt in range(1, int(n/2) + n%2):
            i1 = int(nroots/2) -irt
            i2 = int(nroots/2) +irt  - n%2 
            roots[i1] = -roots[i2]
        return roots 
    

def lagrange(N, a, x, GLL):
    # Computes a'th Lagrange polynomial of degree N at points x
    # using control points specified in GLL array
    poly = 1
    for j in range(0, N+1):
        if j != a:
            poly = poly * ((x - GLL[j]) / (GLL[a] - GLL[j]))
    return poly
/usr/local/Caskroom/miniconda/base/lib/python3.8/site-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.24.3
  warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"

Now let us compute the derivatives using this product rule, and compare it against a finite-difference approach and the formula by Canuto et al 2006, to ensure consistency.

# Compute the Lagrange polynomials for n 
n = 6

# Compute the n+1 GLL points
gllpts = gll(n)

# We will plot the Lagrange polynomials and their derivatives
fig, ax = plt.subplots(2, figsize=(12,8))

# Define domain
x = np.linspace(-1, 1, 1000)

# collect objects for legend
leglines = []

# plot colours: Paul Tol's medium contrast scheme 
clrs = ['k', '#004488',  "#994455", '#997700', 
        '#6699CC', '#EE99AA',  '#EECC66' ]


# Loop over the n+1 polynomials
for i in range(n+1): 
    lag = lagrange(n, i, x, gllpts)
    
    # Plot lagrange polynomials as sanity check
    ax[0].plot(x, lag, color=clrs[i])

    
    # Compute the derivative as a finite difference
    fd = (lag[2:] - lag[:-2])/(x[2]-x[0])
    fd, = ax[1].plot(x[1:-1], fd, color=clrs[i])
    if i ==0: 
        leglines.append(fd)
    
    # Compute the derivative with Product rule formula 
    sum = x*0 
    for j in range(n+1):
        if j!=i: 
            prod = 1 + np.zeros(len(x)) 
            for m in range(n+1): 
                if m != i and m != j: 
                    prod *= (x - gllpts[m])/(gllpts[i] - gllpts[m])
            sum += (1/(gllpts[i] - gllpts[j])) * prod  
    # Plot product rule version 
    prodr, = ax[1].plot(x, sum, '--', color=clrs[i])
    if i ==0: 
        leglines.append(prodr)
    
    
# Computing specifically at GLL points: 
# Using method in Canuto et al 2006
# Loop over polynomials
for i in range(n+1):
    # Loop over GLL points 
    derivs = np.zeros(n+1)
    
    leg_n = ss.legendre(n)
    for j in range(n+1):
        if i == 0 and j == 0:
            derivs[j] = -(n+1)*n/4
        elif i == n and j == n: 
            derivs[j] = (n+1)*n/4
        elif i != j:  
            derivs[j] = leg_n(gllpts[j]) / ( leg_n(gllpts[i]) * (gllpts[j]-gllpts[i]) ) 
        else: 
            derivs[j] = 0 
    
    # Plot
    canuto = ax[1].scatter(gllpts, derivs, marker='o', c=clrs[i])
    if i ==0: 
        leglines.append(canuto)
    
    
    
# Cosmetics
ax[0].set_title(f'Lagrange polynomials of degree {n}');
ax[1].set_title(f'1st derivatives of Lagrange polynomials of degree {n}');
ax[1].legend(leglines, ['Finite difference', 'Product rule', 'Canuto et al., 2006']);
../_images/b92cbf6354955c9dc212a05c8dfecfcb94617797e10eaf3d85565f708a2a775a.png