import numpy as np
from numpy.polynomial import polynomial as P
from sklearn.metrics import mean_squared_error

rng = np.random.default_rng(0)

# ---- synthetic data: a mild "hump" shape plus noise ----
X = np.linspace(0, 10, 11)
true_y = -0.15 * (X - 4.5) ** 2 + 4.5
y = true_y + rng.normal(0, 0.3, size=X.shape)

def fit_and_eval(degree):
    coefs = np.polyfit(X, y, degree)          # highest power first
    y_hat = np.polyval(coefs, X)
    mse = mean_squared_error(y, y_hat)
    return coefs, mse

print(f"{'Degree':<8}{'Model':<12}{'Train MSE':>10}")
for degree, name in [(1, "Underfit"), (2, "Good fit"), (12, "Overfit")]:
    _, mse = fit_and_eval(degree)
    print(f"{degree:<8}{name:<12}{mse:>10.4f}")

# A degree-12 polynomial on 11 points interpolates almost exactly (near-zero
# training error) but will swing wildly between the training x-values --
# exactly the "memorized noise" behaviour shown in the lecture figure.
# Try evaluating each fitted polynomial at a dense grid (e.g. np.linspace(0,10,200))
# and plotting it against X, y with matplotlib to see this directly.
