######################################################################################
#                    Régression multiple et polynomiale                              #
######################################################################################
import random as rd
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

## Importation des données

def Import(fichier):
    f1=open(fichier,'r')
    ligne=f1.readline()
    ligne=f1.readline()
    Tab=[]
    while ligne!='':
        ligne=ligne.strip('\n')
        Liste=ligne.split('\t')
        Tab.append([float(Liste[0]),float(Liste[1]),float(Liste[2])])
        ligne=f1.readline()
    f1.close()
    Donnees=np.array(Tab)
    Entrees,Sorties=Donnees[:,:2],Donnees[:,2:3]
    return Entrees,Sorties

Entrees,Sorties=Import("Donnees_mystere.txt")

## Affichage des données dans un graphique 3D et Création de la matrice de la régression

def Trace_donnees(X,Y,Montrer):
    ax=plt.axes(projection='3d')
    ax.scatter(X[:,0],X[:,1],Y[:,0])
    ax.set_xlabel('Entrées1 = Xa')
    ax.set_ylabel('Entrées2 = Xb')
    ax.set_zlabel('Sorties = Y')
    if Montrer==True:
        plt.show()

def Mat_donnees(entrees_brutes:np.array,L_degres:list)->np.array:
    Ndonnees,Nparam=np.shape(entrees_brutes)
    donnees=np.ones((Ndonnees,1+sum(L_degres)))
    colonne=1
    for ip in range(Nparam):
        degre=1
        for id in range(L_degres[ip]):
            donnees[:,colonne]=(entrees_brutes[:,ip])**degre
            degre+=1
            colonne+=1
    return donnees

def Init_vecteur_parametres(X,Y):
    Nbr_para=np.shape(X)[1]
    Winit=[[np.mean(Y)]]
    Winit=Winit+[[rd.random()] for i in range(1,Nbr_para)]
    return np.array(Winit)

def normalisation_donnes(donnees):
    mu = np.ones(donnees.shape[1])          #  Moyennes des entrées
    sigma = np.zeros(donnees.shape[1])      # Ecart type des entrées
    donnees_normal=np.ones(donnees.shape)   # Matrice des entrées normalisées
    for k in range(1,donnees.shape[1]):     # Pour chaque colonne sauf celle des "1"
        mu[k] = np.mean(donnees[:,k])                   # Calcul de la moyenne
        sigma[k] = np.std(donnees[:,k])                 # Calcul de l'écart type
        #donnees_normal[:,k]=     # Normalisation des données
    return donnees_normal,mu,sigma


## Algorithme de la régression

# def Cout(X,W,Y):





# def gradient(X,W,Y):






def Regression(Entrees,Sorties,L_degre,Nbr_iter,alpha,Epsilon):
        # Construction de la matrice de données
    Xd=Mat_donnees(Entrees,L_degre)
        # Normalisation des données et initialisation du vecteur paramètres W
    Xn,mu,sigma=normalisation_donnes(Xd)
    Wn=Init_vecteur_parametres(Xn,Sorties)
    print('Parametres initiaux de la régression :')
    print(Wn)
        # Initialisation du Delta_coût et du compteur
    cout=Cout(Xn,Wn,Sorties)
    HC=[cout]                   # Historique des coûts
    CP=np.inf                   # Coût précédent (avant la 1iere itération il est infini)
    k=0                         # Compteur d'itérations
    Delta_Cout=abs(CP-HC[-1])   # Variation du coût
    print(cout,'\t',k,'\t','\t',Delta_Cout)
        # Boucle d’apprentissage par la descente de gradiant
    while (k<Nbr_iter and Delta_Cout>Epsilon):
                       # Actualisation du vecteur paramètres
            # Calcul du nouveau Delta_coût
        CP=HC[-1]
        cout=Cout(Xn,Wn,Sorties)
        HC.append(cout)
        Delta_Cout=abs(CP-HC[-1])
            # Affichage du Coût, du compteur et du Delta_coüt
            #  toutes les 1000 itérations
        k+=1    # Incrémentation du compteur d'itération
        if k%1000==0:
            print(cout,'\t',k,'\t',Delta_Cout)
                # Le vecteur paramètres est celui des données normalisées
                # Caclul des paramètres des données avant normalisation
    W=np.zeros((len(Wn),1))
    W[0,0]=Wn[0,0]
    for i in range(1,len(Wn)):
                                       # Pour le calcul du paramètre Omega 0
                                    # pour le calcul du paramètre Omage i
            # Affiche graphique des données et de la régression
    Ecriture_regression(W,k,L_degre,HC)
    graphique(Entrees,Sorties,W,L_degre)
    # return W,HC,k

## Affichage des résultats et des données dans un graphique 3D

def Ecriture_regression(W,k,L_degre,Liste_Couts):
    print("La régression a été construite en",k,"itérations")
    print("Le modèle de prédiction est :")
    equation='y = w0 + w1.xa'
    for j in range(2,L_degre[0]+1):
        equation +=' + w'+str(j)+'.xa^'+str(j)
    equation+=' + w'+str(L_degre[0]+1)+'.xb'
    for j in range(2,L_degre[1]+1):
        equation +=' + w'+str(L_degre[0]+2)+'.xb^'+str(j)
    print(equation)
    print('Les paramètres wi de cette équation sont (de w0 à w'+str(sum(L_degre))+') :')
    print(W)
    print("L'erreur moyenne est :",np.sqrt(Liste_Couts[-1])*2)

def graphique(X,Y,W,L_degres):
    Trace_donnees(X,Y,False)
    ax=plt.axes(projection='3d')
    ax.scatter(X[:,0],X[:,1],Y[:,0])
    ax.set_xlabel('Entrées1 = Xa')
    ax.set_ylabel('Entrées2 = Xb')
    ax.set_zlabel('Sorties = Y')
    f = lambda sx, sy : W[0,0] + sum([W[i+1,0]*sx**(i+1) for i in range(L_degres[0])]) + sum([W[i+1+L_degres[0],0]*sy**(i+1) for i in range(L_degres[1])])
    GX = np.linspace(10,40,31)
    GY = np.linspace(10,40,31)
    GX, GY = np.meshgrid(GX, GY)
    Z = f(GX, GY)
    ax.plot_surface(GX, GY, Z, cmap='rainbow', alpha=0.8)
    plt.show()
