import numpy as np
# import pandas as pd
import math
from random import random
import matplotlib.pyplot as plt

# Part 1
variance = 0.2
standard_deviation = np.sqrt(variance)
mean_value = 0
N = 10
N_vis = 10000

e = np.random.normal(loc=mean_value, scale=standard_deviation, size=(N, 1))
x = []
X = []

f = np.zeros((N,1))
F = np.zeros((N_vis,1))
y = np.zeros((N,1))

for i in range(N):
    x.append(random())
    f[i] = 0.33 * math.cos(1.8 * math.pi * x[i]) + 0.55 * math.sin(6 * math.pi * x[i]) + 0.45
    y[i] = f[i] +e[i]

for k in range(N_vis):
    X.append(random())
    F[k] = 0.33 * math.cos(1.8 * math.pi * X[k]) + 0.55 * math.sin(6 * math.pi * X[k]) + 0.45

M = [1, 3, 5, 7, 8, 9]

weights1 = np.polyfit(x, y, M[0])
approx_function1 = np.polyval(weights1, X);
weights3 = np.polyfit(x, y, M[1]);
approx_function3 = np.polyval(weights3, X);
weights5 = np.polyfit(x, y, M[2]);
approx_function5 = np.polyval(weights5, X);
weights7 = np.polyfit(x, y, M[3]);
approx_function7 = np.polyval(weights7, X);
weights8 = np.polyfit(x, y, M[4]);
approx_function8 = np.polyval(weights8, X);
weights9 = np.polyfit(x, y, M[5]);
approx_function9 = np.polyval(weights9, X);

plt.plot(x, y, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function1, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (polynomial order = 1)')
plt.legend()
plt.show()

plt.plot(x, y, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function3, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (polynomial order = 3)')
plt.legend()
plt.show()

plt.plot(x, y, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function5, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (polynomial order = 5)')
plt.legend()
plt.show()

plt.plot(x, y, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function7, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (polynomial order = 7)')
plt.legend()
plt.show()

plt.plot(x, y, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function8, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (polynomial order = 8)')
plt.legend()
plt.show()

plt.plot(x, y, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function9, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (polynomial order = 9)')
plt.legend()
plt.show()

# Part 2
variance = 0.2
standard_deviation = np.sqrt(variance)
mean_value = 0
M = 9
N = [10, 50, 100, 200, 500]
N_vis = 10000
F = np.zeros((N_vis,1))
X = []
for k in range(N_vis):
    X.append(random())
    F[k] = 0.33 * math.cos(1.8 * math.pi * X[k]) + 0.55 * math.sin(6 * math.pi * X[k]) + 0.45

x1 = []
e1 = np.random.normal(loc=mean_value, scale=standard_deviation, size=(N[0], 1))
f1 = np.zeros((N[0],1))
y1 = np.zeros((N[0],1))
for i in range(N[0]):
    x1.append(random())
    f1[i] = 0.33 * math.cos(1.8 * math.pi * x1[i]) + 0.55 * math.sin(6 * math.pi * x1[i]) + 0.45
    y1[i] = f1[i] +e1[i]

x2 = []
e2 = np.random.normal(loc=mean_value, scale=standard_deviation, size=(N[1], 1))
f2 = np.zeros((N[1],1))
y2 = np.zeros((N[1],1))
for i in range(N[1]):
    x2.append(random())
    f2[i] = 0.33 * math.cos(1.8 * math.pi * x2[i]) + 0.55 * math.sin(6 * math.pi * x2[i]) + 0.45
    y2[i] = f2[i] +e2[i]

x3 = []
e3 = np.random.normal(loc=mean_value, scale=standard_deviation, size=(N[2], 1))
f3 = np.zeros((N[2],1))
y3 = np.zeros((N[2],1))
for i in range(N[2]):
    x3.append(random())
    f3[i] = 0.33 * math.cos(1.8 * math.pi * x3[i]) + 0.55 * math.sin(6 * math.pi * x3[i]) + 0.45
    y3[i] = f3[i] +e3[i]

x4 = []
e4 = np.random.normal(loc=mean_value, scale=standard_deviation, size=(N[3], 1))
f4 = np.zeros((N[3],1))
y4 = np.zeros((N[3],1))
for i in range(N[3]):
    x4.append(random())
    f4[i] = 0.33 * math.cos(1.8 * math.pi * x4[i]) + 0.55 * math.sin(6 * math.pi * x4[i]) + 0.45
    y4[i] = f4[i] +e4[i]

x5 = []
e5 = np.random.normal(loc=mean_value, scale=standard_deviation, size=(N[4], 1))
f5 = np.zeros((N[4],1))
y5 = np.zeros((N[4],1))
for i in range(N[4]):
    x5.append(random())
    f5[i] = 0.33 * math.cos(1.8 * math.pi * x5[i]) + 0.55 * math.sin(6 * math.pi * x5[i]) + 0.45
    y5[i] = f5[i] +e5[i]
    
weights1 = np.polyfit(x1, y1, M)
approx_function1 = np.polyval(weights1, X);
weights2 = np.polyfit(x2, y2, M);
approx_function2 = np.polyval(weights2, X);
weights3 = np.polyfit(x3, y3, M);
approx_function3 = np.polyval(weights3, X);
weights4 = np.polyfit(x4, y4, M);
approx_function4 = np.polyval(weights4, X);
weights5 = np.polyfit(x5, y5, M);
approx_function5 = np.polyval(weights5, X);

plt.plot(x1, y1, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function1, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (N = 10)')
plt.legend()
plt.show()

plt.plot(x2, y2, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function2, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (N = 50)')
plt.legend()
plt.show()

plt.plot(x3, y3, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function3, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (N = 100)')
plt.legend()
plt.show()

plt.plot(x4, y4, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function4, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (N = 200)')
plt.legend()
plt.show()

plt.plot(x5, y5, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function5, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (N = 500)')
plt.legend()
plt.show()

# Part 3
mean_value = 0
M = 9
N = 100
N_vis = 10000
F = np.zeros((N_vis,1))
X = []
for k in range(N_vis):
    X.append(random())
    F[k] = 0.33 * math.cos(1.8 * math.pi * X[k]) + 0.55 * math.sin(6 * math.pi * X[k]) + 0.45

variance = [0, 0.2, 0.6, 0.8, 1]
standard_deviation = np.sqrt(variance)
x1 = []
e1 = np.random.normal(loc=mean_value, scale=standard_deviation[0], size=(N, 1))
f1 = np.zeros((N,1))
y1 = np.zeros((N,1))
for i in range(N):
    x1.append(random())
    f1[i] = 0.33 * math.cos(1.8 * math.pi * x1[i]) + 0.55 * math.sin(6 * math.pi * x1[i]) + 0.45
    y1[i] = f1[i] +e1[i]

x2 = []
e2 = np.random.normal(loc=mean_value, scale=standard_deviation[1], size=(N, 1))
f2 = np.zeros((N,1))
y2 = np.zeros((N,1))
for i in range(N):
    x2.append(random())
    f2[i] = 0.33 * math.cos(1.8 * math.pi * x2[i]) + 0.55 * math.sin(6 * math.pi * x2[i]) + 0.45
    y2[i] = f2[i] +e2[i]

x3 = []
e3 = np.random.normal(loc=mean_value, scale=standard_deviation[2], size=(N, 1))
f3 = np.zeros((N,1))
y3 = np.zeros((N,1))
for i in range(N):
    x3.append(random())
    f3[i] = 0.33 * math.cos(1.8 * math.pi * x3[i]) + 0.55 * math.sin(6 * math.pi * x3[i]) + 0.45
    y3[i] = f3[i] +e3[i]

x4 = []
e4 = np.random.normal(loc=mean_value, scale=standard_deviation[3], size=(N, 1))
f4 = np.zeros((N,1))
y4 = np.zeros((N,1))
for i in range(N):
    x4.append(random())
    f4[i] = 0.33 * math.cos(1.8 * math.pi * x4[i]) + 0.55 * math.sin(6 * math.pi * x4[i]) + 0.45
    y4[i] = f4[i] +e4[i]

x5 = []
e5 = np.random.normal(loc=mean_value, scale=standard_deviation[4], size=(N, 1))
f5 = np.zeros((N,1))
y5 = np.zeros((N,1))
for i in range(N):
    x5.append(random())
    f5[i] = 0.33 * math.cos(1.8 * math.pi * x5[i]) + 0.55 * math.sin(6 * math.pi * x5[i]) + 0.45
    y5[i] = f5[i] +e5[i]
    
weights1 = np.polyfit(x1, y1, M)
approx_function1 = np.polyval(weights1, X);
weights2 = np.polyfit(x2, y2, M);
approx_function2 = np.polyval(weights2, X);
weights3 = np.polyfit(x3, y3, M);
approx_function3 = np.polyval(weights3, X);
weights4 = np.polyfit(x4, y4, M);
approx_function4 = np.polyval(weights4, X);
weights5 = np.polyfit(x5, y5, M);
approx_function5 = np.polyval(weights5, X);


plt.plot(x1, y1, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function1, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (σ^2 = 0)')
plt.legend()
plt.show()

plt.plot(x2, y2, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function2, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (σ^2 = 0.2)')
plt.legend()
plt.show()

plt.plot(x3, y3, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function3, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (σ^2 = 0.6)')
plt.legend()
plt.show()

plt.plot(x4, y4, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function4, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (σ^2 = 0.8)')
plt.legend()
plt.show()

plt.plot(x5, y5, 'r*', label="X - Y")
plt.plot(X, F, 'b.', label="X - f(x)")
plt.plot(X, approx_function4, 'g.', label="X - Approximation function")
plt.xlabel('X')
# plt.ylabel('Cost Function')
plt.title('Y Vs. f(x) Vs. Approximation function (σ^2 = 1)')
plt.legend()
plt.show()