From a6f23f6a619b913e4114df2f10b90bb8c1a57733 Mon Sep 17 00:00:00 2001
From: Gabriel Gros <gabriel.gros@student-cs.fr>
Date: Tue, 1 Apr 2025 16:17:37 +0200
Subject: [PATCH] =?UTF-8?q?Ajout=20pr=C3=A9conditionnement?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 loss_function.py | 165 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 153 insertions(+), 12 deletions(-)

diff --git a/loss_function.py b/loss_function.py
index 59ef63a..40456b7 100644
--- a/loss_function.py
+++ b/loss_function.py
@@ -2,6 +2,7 @@ import numpy as np
 import matplotlib.pyplot as plt
 import math as mt
 from itertools import permutations
+import numdifftools as nd
 
 
 def generate_data_multi(n, means, scale, weights, distribution='gaussian', kernel=None):
@@ -224,7 +225,7 @@ def batch_algo_gradient_descent(m, n, means, weights, num_steps=100, eta=0.1):
 
     # Dictionary function Phi: for a given tau vector (r,), returns a matrix (N_freq x r)
     def Phi(tau):
-        return np.exp(2j * np.pi * np.outer(freqs.flatten(), tau))
+        return np.exp(-2j * np.pi * np.outer(freqs.flatten(), tau))
 
     # Model function: Y_model = G * Phi(tau) * a.
     def Y_model(a, tau):
@@ -300,7 +301,7 @@ def batch_algo_gradient_descent_without_estimator(m, n, means, weights,est, num_
 
     # Dictionary function Phi: for a given tau vector (r,), returns a matrix (N_freq x r)
     def Phi(tau):
-        return np.exp(2j * np.pi * np.outer(freqs.flatten(), tau))
+        return np.exp(-2j * np.pi * np.outer(freqs.flatten(), tau))
 
     # Model function: Y_model = G * Phi(tau) * a.
     def Y_model(a, tau):
@@ -582,7 +583,7 @@ def show_loss_path(tau_est,tau_star,tau_min,tau_max,nb_values,m,n,num_steps =100
 
 
 
-def show_conv_speed(tau_est,tau_star,m,n,num_steps =1000,eta=0.1):
+def show_conv_speed_tau(tau_est,tau_star,m,n,num_steps =1000,eta=0.1):
     """
     We take r=2, and let a1 = a2 = 1/2.
 
@@ -670,8 +671,6 @@ def show_conv_speed(tau_est,tau_star,m,n,num_steps =1000,eta=0.1):
     
         grad_tau = np.real(( (a_est.conj()) * (( Lambda @ G_mat @ Phi(tau_est)).conj().T) ) @ r_vec)
 
-        
-
         # Preconditioner for tau-update:
         P_tau = np.diag(1/(-Kpp0) * ((a_est.flatten())**2))
         #P_tau = 1
@@ -706,16 +705,15 @@ def show_conv_speed(tau_est,tau_star,m,n,num_steps =1000,eta=0.1):
 
 
 
-def precond_diag(a_k,freqs,r,n):
+def precond_diag(a_k,tau_k,freqs,r,n):
     """
     Build the adaptative diagonal preconditionner matrix.
     """
     K_second_0 = 0
-    N = 2*n+1
-    L=fourier(freqs).flatten()
-    for k in range(N):
-        K_second_0 += (abs(L[k])**2) * (k-n)/N
-    K_second_0 = (-4*np.pi**2)*K_second_0
+ 
+    for f in freqs.flatten():
+        K_second_0 += (abs(fourier(f))**2) * (f**2) * (-4*(np.pi**2))
+    
 
     P = np.eye(2*r)
 
@@ -725,9 +723,152 @@ def precond_diag(a_k,freqs,r,n):
     return P
 
 
-def precond_full(a_k,freqs,r,n):
+def precond_full(a_k,tau_k,freqs,r,n):
     """
     Build the adaptative full preconditionner matrix.
     """
+    N=2*n+1
+
 
     Ak = np.diag(np.concatenate((np.array([1 for _ in range(r)]), np.array(a_k))))
+    
+
+    G_diag = fourier(freqs).flatten()  # shape (N_freq,)
+    G_mat = np.diag(G_diag)  # shape (N_freq, N_freq)
+
+    Phi= np.exp(-2j * np.pi * np.outer(freqs.flatten(), tau_k))
+
+
+
+    diag_elements = -2j * np.pi * (np.arange(N) - n) / N
+    Lambda = np.diag(diag_elements)
+    
+
+    W = np.hstack((G_mat @ Phi , Lambda @ G_mat @ Phi))
+
+    P = np.linalg.inv( Ak.conj().T @ ((W.conj().T) @ W) @ Ak)
+
+    return P
+
+
+def matrix_S(freqs,a_star,n,r):
+    """
+    Buils the matrix S that adimensionnate the space : With S, the convergence of the gradient descent is linear.
+    """
+    K_second_0 = 0
+ 
+    for f in freqs.flatten():
+        K_second_0 += (abs(fourier(f))**2) * (f**2) * (-4*(np.pi**2))
+    
+    S = np.diag(np.concatenate(([1/k for k in a_star] , [np.sqrt(-K_second_0) for _ in range(r)])))
+
+    return S
+
+
+
+
+def show_conv_speed(theta_est,theta_star,n,precond,num_steps =2000,eta=0.1):
+    """
+    We fix a preconditionner precond, and we look at the convergence of the gradient descent wrt to this preconditionner.
+    We assume here the data is perfect : m is useless
+    """
+    assert len(theta_star) == len(theta_est) , "Error: star and est not same size"
+    assert len(theta_star)%2 ==0 , "Error : r is not even"
+
+    r = len(theta_star)//2
+    freqs=sample_uniform_frequencies(n)
+
+    #Make the input np.array
+    theta_star = np.array(theta_star)
+    theta_est = np.array(theta_est)
+
+    tau_star = theta_star[r:]
+    a_star = theta_star[:r]
+    N=2*n+1
+
+    tau_est = theta_est[r:]
+    a_est = theta_est[:r]
+
+
+    #Reshape the a vectors to make computations easier
+    a_est = a_est.reshape(-1, 1)
+    a_star = a_star.reshape(-1,1)
+
+    S = matrix_S(freqs,a_star.flatten(),n,r)
+    #vitesse_conv = [permute_and_compute_norm(theta_est,theta_star,r)[0]]
+
+    vitesse_conv = [permute_and_compute_norm( (S @ theta_est.reshape(-1,1)).flatten() ,(S @ theta_est.reshape(-1,1)).flatten() ,r)[0]]
+
+    # Precompute the "G" matrix: diagonal with entries given by kernel's Fourier transform at freqs.
+    G_diag = fourier(freqs).flatten()  # shape (N_freq,)
+    G_mat = np.diag(G_diag)  # shape (N_freq, N_freq)
+
+    # Dictionary function Phi: for a given tau vector (r,), returns a matrix (N_freq x r)
+    def Phi(tau):
+        return np.exp(-2j * np.pi * np.outer(freqs.flatten(), tau))
+
+    # Model function: Y_model = G * Phi(tau) * a.
+    def Y_model(a, tau):
+        return G_mat @ (Phi(tau) @ a)
+
+    Y = Y_model(a_star,tau_star) # No noise assumed, perfect data m -> +inf
+ 
+    # Run batch gradient descent updates 
+    for step in range(num_steps):
+        # Compute the current model prediction
+        Y_estimated = Y_model(a_est, tau_est)
+
+        # Residual: difference between model prediction and observed FCE
+        r_vec = Y_estimated - Y  # shape (N_freq, 1)
+
+        # Gradient with respect to amplitudes: grad_a = (G*Phi(tau))^H * r
+        grad_a = (G_mat @ Phi(tau_est)).conj().T @ r_vec  # shape (r, 1)
+
+        # Gradient with respect to tau:
+        diag_elements = -2j * np.pi * (np.arange(N) - n) / N
+        Lambda = np.diag(diag_elements)
+
+        grad_tau = np.real(( (a_est.conj()) * (( Lambda @ G_mat @ Phi(tau_est)).conj().T) ) @ r_vec)
+
+        #Concatenation of both gradients
+        grad = np.vstack((grad_a,grad_tau))
+        
+        def Loss_theta(theta):
+            return loss(theta,theta_star,r,freqs)
+
+        # Gradient numérique si besoin
+        #grad2 = nd.Gradient(Loss_theta)(theta_est) 
+
+        #Preconditionner for the update
+        P = precond(a_est.flatten(),tau_est,freqs,r,n)
+       
+        # Gradient descent update with learning rate eta    
+        theta_est = theta_est - eta * (P @ grad).flatten()
+        ##theta_est = theta_est -eta * grad.flatten()
+        a_est = theta_est[:r].reshape(-1,1)
+        tau_est = theta_est[r:]
+
+        
+        theta_est = np.concatenate((a_est.flatten(),tau_est))
+
+
+        #vitesse_conv.append(permute_and_compute_norm(theta_est,theta_star,r)[0])
+        vitesse_conv.append(permute_and_compute_norm( (S @ theta_est.reshape(-1,1)).flatten() ,(S @ theta_est.reshape(-1,1)).flatten() ,r)[0])
+    
+    
+    # Final estimated parameters (concatenated amplitudes and centroids)
+    theta_est_final = np.concatenate((a_est.flatten(), tau_est))
+
+    norm , theta_est_final_prime = permute_and_compute_norm(theta_est_final,theta_star,r)
+    
+
+    print(vitesse_conv)
+    plt.plot( vitesse_conv , label = f"{precond}")
+    plt.yscale('log')
+    plt.xlabel("Itération")
+    plt.ylabel(f"Norme de la fonction de perte (échelle log)")
+    plt.legend()
+    plt.show()
+
+    return norm, theta_est_final , theta_est_final_prime
+
-- 
GitLab