Orthogonal polynomials

Orthogonal polynomials#

Generate a random polynomial of degree \(k\) and then fit it with either Legendre or Chebyshev polynomials of degree \(k_\mathrm{fit}\). Because our domain goes from \(x=[-2,2]\) whereas the basis functions are defined on \([-1,1]\), we need to rescale \(x\) when evaluating the basis functions.

import numpy as np
import matplotlib.pyplot as plt
# first generate some data from a random polynomial
k = 20  # order of polynomial
n = 100   # number of data points

kfit = 18  # number of basis functions for the fit

x = np.linspace(-2,2,n)
a0 = -1.0 + 2*np.random.rand(k+1)
poly = np.polynomial.Polynomial(a0)
y = poly(x) + np.random.normal(scale=0.0, size=n)

# OR try a more complicated function
y = np.exp(np.sin(x**3)) + np.tanh(x)**3
def do_fit(A, y, use_svd = True):
    
    if use_svd:
        U, Sdiag, VT = np.linalg.svd(A,0)
        a = VT.T @ np.diag(1/Sdiag) @ U.T @ y
    else:
        rhs = np.transpose(A)@y
        lhs = np.transpose(A)@A
        a = np.linalg.inv(lhs)@rhs

    return a
plt.plot(x,y,'.')

# Fit with Chebyshev polynomials
A = np.polynomial.chebyshev.chebvander(x/2,kfit)

print("Condition number = %g" % (np.linalg.cond(A)))

a_cheb = do_fit(A, y)
      
print("Chebyshev coefficients:")
for i in range(len(a_cheb)):
    print("a%d %g" % (i, a_cheb[i]))
    
xx = np.linspace(-2,2,1000)
poly_cheb = np.polynomial.Chebyshev(a_cheb)
yy = poly_cheb(xx/2)
plt.plot(xx,yy,'--')

dev = np.sqrt(np.mean((y-poly_cheb(x/2))**2))
print('rms deviation / max(y) = %.10g' % (dev/np.max(np.abs(y)),))

plt.show()
Condition number = 3.00281
Chebyshev coefficients:
a0 1.24939
a1 1.3663
a2 0.176774
a3 -0.00196591
a4 -0.0258484
a5 0.607558
a6 0.149407
a7 0.622783
a8 0.0643741
a9 -0.28448
a10 0.00928857
a11 -0.296327
a12 0.114861
a13 -0.0143096
a14 -0.00301711
a15 0.0464009
a16 -0.150359
a17 0.0118609
a18 -0.081184
rms deviation / max(y) = 0.01052955567
_images/74d6fd3922343a1eedfd330b223f783cf7e15a331b48f7bfa34eeaf78277f8fa.png
plt.plot(x,y,'.')

# Fit with Legendre polynomials
A = np.polynomial.legendre.legvander(x/2,kfit)

print("Condition number = %g" % (np.linalg.cond(A)))

a_leg = do_fit(A, y)
      
print("Legendre coefficients:")
for i in range(len(a_leg)):
    print("a%d %g" % (i, a_leg[i]))
    
xx = np.linspace(-2,2,1000)
poly_leg = np.polynomial.Legendre(a_leg)
yy = poly_leg(xx/2)
plt.plot(xx,yy,'--')

dev = np.sqrt(np.mean((y-poly_leg(x/2))**2))
print('rms deviation / max(y) = %.10g' % (dev/np.max(np.abs(y)),))

plt.show()
Condition number = 6.03442
Legendre coefficients:
a0 1.18685
a1 1.25736
a2 0.22062
a3 -0.633234
a4 -0.214353
a5 0.672949
a6 0.248942
a7 1.9391
a8 0.135166
a9 -0.353199
a10 -0.103653
a11 -0.8796
a12 0.440113
a13 -0.128661
a14 0.285391
a15 0.139449
a16 -0.388023
a17 0.0436596
a18 -0.307374
rms deviation / max(y) = 0.01052955567
_images/74d6fd3922343a1eedfd330b223f783cf7e15a331b48f7bfa34eeaf78277f8fa.png