
# chebRunge.py
# Andrew Davison, ad@coe.psu.ac.th, June 2026
'''
Generate a Chebyshev polynomial approximation to
f(x) in the range [a,b] using 6, 11, and 16 
term approximations, and plot those curves.

f(x) was suggested by Runge, and its approximations have a 
tendency to oscillate with uniformly spaced zeros, but
much less so with Chebyshev zeros.

https://en.wikipedia.org/wiki/Runge%27s_phenomenon
'''

import math
import matplotlib.pyplot as plt
import faUtils

def f(x):
  return 1/(1 + 25*x*x)


a, b = -1.0, 1.0
xs = faUtils.linspace(a, b, 1000)
ys = [f(x) for x in xs]

coefs = faUtils.chebFit(a, b, 6, f)
print(f"6-term approx in [{a},{b}]:")
faUtils.printCoefs(coefs)

# Plot f(x)
plt.plot(xs, ys, label='f(x) = 1/(1 + 25x^2)', color='black', lw=2)

# Fit with different no of terms
for nTerms in [6, 11, 16]:
  coefs = faUtils.chebFit(a, b, nTerms, f)
  
  # Evaluate Chebyshev approx
  ysApprox = [faUtils.chebEval(a, b, coefs, nTerms, x) for x in xs]
  
  maxErr = max(abs(y-ya) for y, ya in zip(ys, ysApprox))
  print(f"No. Terms {nTerms}; Max error = {maxErr:.2e}")
  
  plt.plot(xs, ysApprox, label=f'Chebyshev n={nTerms}')

plt.xlabel('x')
plt.ylabel('y')
plt.title(r'Chebyshev Approx of $\frac{{1}}{{1 + 25x^2}}$')
plt.legend()
plt.grid(True)
plt.tight_layout()

plt.show()