def estimate_network(A, nbre_noeuds, L, Delta, T, type_diffusion):

    num_cascades =  numpy.zeros((1,nbre_noeuds))
    Psi2 = numpy.zeros((nbre_noeuds, nbre_noeuds))
    Psi1 = numpy.zeros((nbre_noeuds, nbre_noeuds))
    T3 = list()

    i = 0
    nbre_infections = 0
    max_infections = 0
    while i < nbre_noeuds:
        nbre_infections += len(L[i])
        if len(L[i]) > max_infections:
            max_infections = len(L[i])
        i += 1

    T3 = []
    i = 0
    while i < max_infections:
        T3.append(numpy.zeros((nbre_noeuds,nbre_noeuds)))
        i += 1

    L_sorted = []
    L_int = copy.copy(L)
    L_non_touche = []
    i = 0
    j = 0
    while i < nbre_noeuds:
        while j < len(L[i]):
            L_int[i][j] = (i,j,L[i][j])
            j += 1
        L_sorted += L_int[i]
        if len(L[i]) == 0:
            L_non_touche.append(i)
        i += 1
        j = 0

    L_sorted.sort(key = lambda tup : tup[2])

    c = 0
    X = []
    i_int = 0
    j_int = 0
    tho_int = 0.
    while c < nbre_infections:
        for (i_int,j_int,tho_int) in X:
            if tho_int + Delta[i_int][j_int] < L_sorted[c][2] :
                X.remove((i_int,j_int,tho_int))
            else:
                if type_diffusion == 'exp':
                    Psi2[L_sorted[c][0]][i_int] += L_sorted[c][2] - tho_int
                    T3[L_sorted[c][1]][L_sorted[c][0]][i_int] = 1
                elif type_diffusion == 'pl' and L_sorted[c][2] - tho_int > 1 :
                    Psi2[L_sorted[c][0]][i_int] += math.log(L_sorted[c][2] - tho_int)
                    T3[L_sorted[c][1]][L_sorted[c][0]][i_int] = 1. / (L_sorted[c][2] - tho_int)
                elif type_diffusion == 'rayleigh':
                    Psi2[L_sorted[c][0]][i_int] += 0.5*((L_sorted[c][2] - tho_int)**2)
                    T3[L_sorted[c][1]][L_sorted[c][0]][i_int] = 1. * (L_sorted[c][2] - tho_int)
        if L_sorted[c][2] > 0:
            X.append(L_sorted[c])

        for i in L_non_touche:
            if type_diffusion == 'exp':
                Psi1[L_sorted[c][0]][i] += T - L_sorted[c][2]
            elif type_diffusion == 'pl':
                Psi1[L_sorted[c][0]][i] += math.log(T - L_sorted[c][2])
            elif type_diffusion == 'rayleigh':
                Psi1[L_sorted[c][0]][i] += (T - L_sorted[c][2])**2

        c += 1

    """As explained in the presentation, we do an optimization algorithm by itering the calculus.
    Because we only want to compute Psi1 and Psi2 one time we have developed formation_densite which
    uses the global variables Psi1 and Psi2 and a matrix B.
    It returns the value and the gradient of the log-likelihood"""
    def formation_densite(Beta=numpy.ones((nbre_noeuds,nbre_noeuds))):

        Psi2_grad = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi1_grad = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi3_grad = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi1_intermediaire = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi2_intermediaire = numpy.zeros((nbre_noeuds,nbre_noeuds))
        grad = numpy.zeros((nbre_noeuds,nbre_noeuds))

        Psi1_grad = copy.copy(Psi1)
        Psi1_intermediaire = copy.copy(numpy.dot(-Psi1,Beta.transpose()))

        Psi2_grad = copy.copy(Psi2.transpose())
        Psi2_intermediaire = copy.copy(numpy.dot(-Psi2,Beta))

        produit_densite = list()
        produit_densite_log = list()
        produit_gradient = numpy.zeros((nbre_noeuds,nbre_noeuds))

        c = 0
        while c < max_infections:
            produit_densite.append(copy.copy(numpy.dot(T3[c],Beta)))
            produit_gradient = produit_gradient + matrix_inverse_elements(produit_densite[c],T3[c])
            produit_densite_log.append(copy.copy(log_elements(produit_densite[c])))
            c += 1

        grad = copy.copy((Psi1_grad + Psi2_grad) - (produit_gradient))

        for i in range(len(grad)):
            for j in range(len(grad[i])):
                if Beta[i,j] == 0:
                    grad[i,j] = 0

        return ((- somme_diag_elements(Psi1_intermediaire) - somme_diag_elements(Psi2_intermediaire) - somme_diag_elements(sum(produit_densite_log)))/max_infections, grad/max_infections)

    """We then take a initial matrix and we operate a gradient-descent on it to find the estimated matrix"""
    Beta = numpy.ones((nbre_noeuds,nbre_noeuds))
    (value,gradient) = formation_densite(Beta)
    gradient_initial = norm_matrix(gradient,1)
    gradient_intermediaire = 0.00000001
    k = 0
    while norm_matrix(gradient,1) > 0.1 and norm_matrix(gradient,1) < 4 * gradient_initial and abs((norm_matrix(gradient,1) - gradient_intermediaire)/gradient_intermediaire) > 0.000005:
        Beta = soft_thres(Beta-0.0005*gradient,0.000001)
        for i in range(0,len(Beta)):
            for j in range(0,len(Beta[i])):
                if Beta[i,j] < 0:
                    Beta[i,j] = 0
        if k >= 1:
            gradient_intermediaire = norm_matrix(gradient,1)
        (value,gradient) = formation_densite(Beta)
        k += 1

    print('optimization completed')

    """In order to compare the result with the estimated matrix, we use the following indicators"""
    accuracy = 1.0
    acc_num = 0.
    acc_den = 0.
    MAE = 0.
    MAS = 0.
    for i in range (0,len(A)):
        for j in range(0,len(A)):
            if A[i,j] > 0 and Beta[i,j] > 0:
                acc_den
            elif A[i,j] > 0 or Beta[i,j] > 0:
                acc_num += 1.
                acc_den += 1.

            if A[i,j] > 0:
                MAE += abs(A[i,j] - Beta[i,j]) / A[i,j]
            if Beta[i,j] > 0:
                MAS += abs(A[i,j] - Beta[i,j]) / Beta[i,j]

    if acc_den > 0:
        accuracy -= acc_num / acc_den

    return (Beta,(accuracy,MAE,MAS))
def estimate_network_NetRate(A, C, nbre_noeuds, T, type_diffusion, nbre_infections,mu,return_value=False):

    Psi2 = numpy.zeros((nbre_noeuds, nbre_noeuds))
    Psi1 = numpy.zeros((nbre_noeuds, nbre_noeuds))
    T3 = list()
    nbre_touche_potentiel = numpy.zeros((nbre_noeuds,nbre_noeuds))
    c = 0

    indice_dernier_noeud_touche = 0
    while c < nbre_infections:
        i = nbre_noeuds - 1
        mat_int = C[c].argsort()
        T3.append(numpy.zeros((nbre_noeuds,nbre_noeuds)))
        while (i >= 0 and (C[c][mat_int[i]] > -1)) :
            j = 0
            while j < i:
                nbre_touche_potentiel[mat_int[j],mat_int[i]] += 1
                if (C[c][mat_int[j]] != -1):
                    if(type_diffusion == 'exp'):
                        Psi2[mat_int[i]][mat_int[j]] +=  C[c][mat_int[i]] - C[c][mat_int[j]]
                        T3[c][mat_int[i]][mat_int[j]] = 1
                    elif (type_diffusion == 'pl') and (C[c][mat_int[i]]-C[c][mat_int[j]] > 1):
                        Psi2[mat_int[i]][mat_int[j]] += math.log(C[c][mat_int[i]]-C[c][mat_int[j]])
                        T3[c][mat_int[i]][mat_int[j]] = 1 / (C[c][mat_int[i]]-C[c][mat_int[j]])
                    elif (type_diffusion == 'rayleigh'):
                        Psi2[mat_int[i]][mat_int[j]] += 0.5*((C[c][mat_int[i]] - C[c][mat_int[j]])*(C[c][mat_int[i]] - C[c][mat_int[j]]))
                        T3[c][mat_int[i]][mat_int[j]] = C[c][mat_int[i]]-C[c][mat_int[j]]
                j += 1
            i -= 1

        indice_dernier_noeud_touche = i + 1
        #Dans le calcul de Psi1, exceptionnellement on compte dans le sens inverse
        while i >= 0: #On obtient des j qui ont une valeur nulle
            j = nbre_noeuds - 1
            while j >= indice_dernier_noeud_touche :
                if type_diffusion == 'exp':
                    Psi1[mat_int[j]][mat_int[i]] += T - C[c][mat_int[j]]
                elif type_diffusion == 'pl' and T > 1:
                    Psi1[mat_int[j]][mat_int[i]] += math.log(T-C[c][mat_int[j]])
                elif type_diffusion == 'rayleigh':
                    Psi1[mat_int[j]][mat_int[i]] += 0.5*(T-C[c][mat_int[j]])*(T-C[c][mat_int[j]]);
                j -= 1
            i -= 1

        c += 1

    def formation_densite(Beta=numpy.ones((nbre_noeuds,nbre_noeuds))):
        Psi2_grad = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi1_grad = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi3_grad = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi1_intermediaire = numpy.zeros((nbre_noeuds,nbre_noeuds))
        Psi2_intermediaire = numpy.zeros((nbre_noeuds,nbre_noeuds))
        grad = numpy.zeros((nbre_noeuds,nbre_noeuds))

        Psi1_grad = copy.copy(Psi1)
        Psi1_intermediaire = copy.copy(numpy.dot(-Psi1,Beta.transpose()))

        Psi2_grad = copy.copy(Psi2.transpose())
        Psi2_intermediaire = copy.copy(numpy.dot(-Psi2,Beta))

        produit_densite = list()
        produit_densite_log = list()
        produit_gradient = numpy.zeros((nbre_noeuds,nbre_noeuds))

        c = 0
        while c < nbre_infections:
            produit_densite.append(copy.copy(numpy.dot(T3[c],Beta)))
            produit_gradient = produit_gradient + matrix_inverse_elements(produit_densite[c],T3[c])
            produit_densite_log.append(copy.copy(log_elements(produit_densite[c])))
            c += 1

        grad = copy.copy((Psi1_grad + Psi2_grad) - (produit_gradient))

        for i in range(len(grad)):
            for j in range(len(grad[i])):
                if Beta[i,j] == 0:
                    grad[i,j] = 0


        def hessienne():
            while c < nbre_infections:
                i = 0
                j = 0
                k = 0
                while i < nbre_noeuds:
                    j = 0
                    while j < nbre_noeuds:
                        k = 0
                        while k < nbre_noeuds:
                            if produit_densite[c][j,j] != 0:
                                hessienne[i*nbre_noeuds+j][k*nbre_noeuds+j] += T3[c][i,j]*T3[c][k,j]/(produit_densite[c][j,j]**2)
                            k += 1
                        j += 1
                    i += 1
                c+=1

        return ((- somme_diag_elements(Psi1_intermediaire) - somme_diag_elements(Psi2_intermediaire) - somme_diag_elements(sum(produit_densite)))/nbre_infections, grad/nbre_infections)

    """We then take a initial matrix and we operate a gradient-descent on it to find the estimated matrix"""
    Alpha2 = 0.5 *numpy.ones((nbre_noeuds,nbre_noeuds))
    i = 0
    j = 0
    while i < nbre_noeuds:
        while j < nbre_noeuds:
            if nbre_touche_potentiel[i,j] == 0:
                Alpha2[i,j] = 0
            j+=1
        i += 1
        j = 0

    k = 0
    (value,gradient) = formation_densite(Alpha2)
    value_int = 0
    gradient_intermediaire = 0.00001

    while k < 10000 and norm_matrix(gradient,1) > 0.1:
        Alpha2 = copy.copy(soft_thres(Alpha2 - 0.0005 * gradient,mu))
        i = 0
        while i < len(Alpha2):
            while j < len(Alpha2[i]):
                if Alpha2[i,j] < 0:
                    Alpha2[i,j] = 0
                j += 1
            i += 1
            j = 0

        (value,gradient) = formation_densite(Alpha2)
        if k % 1000 == 0:
            print('GRADIENT -> {}'.format(norm_matrix(gradient,1)))
            print('DIFF --> {}'.format(abs((norm_matrix(gradient,1) - gradient_intermediaire)/gradient_intermediaire)))
            print(value)
            if k % 500000 == 0:
                print(k/500000)
        if k % 1000 == 0 and k > 0:
            print(k)
        k += 1

    if return_value:
        return Alpha2,value
    else:
        return Alpha2