From 44b8f551d2bddc6dee6e102e250d1cf9a32f6b1b Mon Sep 17 00:00:00 2001
From: Tizian Wenzel <tizian.wenzel@ians.uni-stuttgart.de>
Date: Thu, 9 Feb 2023 18:40:36 +0100
Subject: [PATCH] Some improvements for the case when not a list of kernels is
 provided.

---
 example_files/01_testfile.py |  2 +-
 vkoga_2L.py                  | 88 ++++++++++++++++--------------------
 2 files changed, 40 insertions(+), 50 deletions(-)

diff --git a/example_files/01_testfile.py b/example_files/01_testfile.py
index 50bfe80..64b944f 100644
--- a/example_files/01_testfile.py
+++ b/example_files/01_testfile.py
@@ -3,7 +3,7 @@
 import numpy as np
 from matplotlib import pyplot as plt
 import kernels, tkernels
-from vkoga import VKOGA_2L
+from vkoga_2L import VKOGA_2L
 
 np.random.seed(1)
 
diff --git a/vkoga_2L.py b/vkoga_2L.py
index 6625bb2..fbd6f3f 100644
--- a/vkoga_2L.py
+++ b/vkoga_2L.py
@@ -7,31 +7,37 @@ from sklearn.utils.validation import check_X_y, check_array, check_is_fitted
 from scipy.spatial import distance_matrix
 import torch
 from utilities import OptimizedKernel
-    
+
+
 # VKOGA implementation
 class VKOGA_2L(BaseEstimator):
-                                          
+
     def __init__(self, kernel=Matern(k=2), flag_2L_optimization=False,
                  verbose=True, n_report=10,
                  greedy_type='f_greedy', reg_par=0, restr_par=0,
                  tol_f=1e-10, tol_p=1e-10,
                  reg_para_optim=1e-3, learning_rate=5e-3, n_epochs_optim=10, batch_size=32):
-        
+
         # Set the verbosity on/off
         self.verbose = verbose
-        
+
         # Set the frequency of report
         self.n_report = n_report
-        
-        # Set the params defining the method 
+
+        # Set the params defining the method
         self.flag_2L_optimization = flag_2L_optimization
-        if self.flag_2L_optimization:
-            assert type(kernel) is type([]), 'If flag_2L_optimization, then two kernels need to be provided!'
+        if self.flag_2L_optimization and type(kernel) is type([]):
             self.kernel_t = kernel[1]
             self.kernel = kernel[0]
         else:
             self.kernel_t = None
-            self.kernel = kernel
+            if type(kernel) is type([]):
+                print('If kernel optimization is desired, set flag_2L_optimization to True!')
+                self.kernel = kernel[0]
+            else:
+                print('If kernel optimization is desired, provide numpy and torch kernel!')
+                self.flag_2L_optimization = False
+                self.kernel = kernel
 
         self.greedy_type = greedy_type
         self.reg_par = reg_par
@@ -53,16 +59,15 @@ class VKOGA_2L(BaseEstimator):
         self.Cut_ = None
         self.c = None
 
-
         # Initialize the convergence history
         self.train_hist = {}
         self.train_hist['n'] = []
         self.train_hist['f'] = []
         self.train_hist['p'] = []
-        self.train_hist['p selected'] = []              # list of selected power vals
+        self.train_hist['p selected'] = []  # list of selected power vals
         self.train_hist['f val'] = []
         self.train_hist['p val'] = []
-        
+
     def selection_rule(self, f, p):
         if self.restr_par > 0:
             p_ = np.max(p)
@@ -85,7 +90,7 @@ class VKOGA_2L(BaseEstimator):
             f_max = np.max(f)
             idx = np.argmax(p)
             p_max = p[idx]
-        elif self.greedy_type == 'rand':    # pick some random point - David Holzmüller asked me about its performance
+        elif self.greedy_type == 'rand':  # pick some random point - David Holzmüller asked me about its performance
             f_max = np.max(f)
             p_max = np.max(p)
             idx = np.random.randint(len(p))
@@ -110,14 +115,12 @@ class VKOGA_2L(BaseEstimator):
             if len(y_val.shape) == 1:
                 y_val = y_val[:, None]
 
-
         # Check whether already fitted - restart in case we used kernel optimization
         if self.ctrs_ is None or self.flag_2L_optimization:
             self.ctrs_ = np.zeros((0, X.shape[1]))
             self.Cut_ = np.zeros((0, 0))
             self.c = np.zeros((0, y.shape[1]))
 
-
         # Check whether "new X" and previously chosen centers overlap
         list_truly_new_X = []
         if self.ctrs_.shape[0] > 0:
@@ -131,7 +134,6 @@ class VKOGA_2L(BaseEstimator):
         X = X[list_truly_new_X, :]
         y = y[list_truly_new_X, :]
 
-
         # Optimize the kernel
         if self.flag_2L_optimization:
             model_OptimKernel = OptimizedKernel(kernel=self.kernel_t, dim=X.shape[1],
@@ -144,12 +146,11 @@ class VKOGA_2L(BaseEstimator):
 
             self.A = model_OptimKernel.A.detach().numpy()
 
-            X = X @ self.A      # we can incorporate the kernel modification directly into the data
+            X = X @ self.A  # we can incorporate the kernel modification directly into the data
 
         else:
             self.A = np.eye(X.shape[1])
 
-
         # Initialize the residual and update the given y values by substracting the current model
         y = np.array(y)
         if len(y.shape) == 1:
@@ -158,45 +159,38 @@ class VKOGA_2L(BaseEstimator):
         if self.flag_val:
             y_val = y_val - self.predict(X_val)
 
-
         # Get the data dimension
         N, q = y.shape
         if self.flag_val:
             N_val = y_val.shape[0]
 
-
         # Set maxIter_continue
         if maxIter is None or maxIter > N:
             self.maxIter = 100
         else:
             self.maxIter = maxIter
 
-
         # Check compatibility of restriction
         if self.greedy_type == 'p_greedy':
             self.restr_par = 0
         if not self.reg_par == 0:
             self.restr_par = 0
 
-
         # Initialize list for the chosen and non-chosen indices
         indI_ = []
         notIndI = list(range(N))
         c = np.zeros((self.maxIter, q))
 
-
         # Compute the Newton basis values (related to the old centers) on the new X
         Vx_new_X_old_ctrs = self.kernel.eval(X, self.ctrs_) @ self.Cut_.transpose()
         if self.flag_val:
             Vx_val_new_X_old_ctrs = self.kernel.eval(X_val, self.ctrs_) @ self.Cut_.transpose()
 
-
         # Initialize arrays for the Newton basis values (related to the new centers) on the new X
         Vx = np.zeros((N, self.maxIter))
         if self.flag_val:
             Vx_val = np.zeros((N_val, self.maxIter))
 
-
         # Compute the powervals on X and X_val
         p = self.kernel.diagonal(X) + self.reg_par
         p = p - np.sum(Vx_new_X_old_ctrs ** 2, axis=1)
@@ -204,13 +198,11 @@ class VKOGA_2L(BaseEstimator):
             p_val = self.kernel.diagonal(X_val) + self.reg_par
             p_val = p_val - np.sum(Vx_val_new_X_old_ctrs ** 2, axis=1)
 
-
         # Extend Cut_ matrix, i.e. continue to build on old self.Cut_ matrix
         N_ctrs_so_far = self.Cut_.shape[0]
         Cut_ = np.zeros((N_ctrs_so_far + self.maxIter, N_ctrs_so_far + self.maxIter))
         Cut_[:N_ctrs_so_far, :N_ctrs_so_far] = self.Cut_
 
-
         # Iterative selection of new points
         self.print_message('begin')
         for n in range(self.maxIter):
@@ -244,19 +236,18 @@ class VKOGA_2L(BaseEstimator):
                 break
 
             # compute the nth basis (including normalization)# ToDo: Also old Vx need to be substracted here!
-            Vx[notIndI, n] = self.kernel.eval(X[notIndI, :], X[indI_[n], :])[:, 0]\
-                 - Vx_new_X_old_ctrs[notIndI, :] @ Vx_new_X_old_ctrs[indI_[n], :].transpose()\
-                 - Vx[notIndI, :n+1] @ Vx[indI_[n], 0:n+1].transpose()
+            Vx[notIndI, n] = self.kernel.eval(X[notIndI, :], X[indI_[n], :])[:, 0] \
+                             - Vx_new_X_old_ctrs[notIndI, :] @ Vx_new_X_old_ctrs[indI_[n], :].transpose() \
+                             - Vx[notIndI, :n + 1] @ Vx[indI_[n], 0:n + 1].transpose()
             Vx[indI_[n], n] += self.reg_par
             Vx[notIndI, n] = Vx[notIndI, n] / np.sqrt(p[indI_[n]])
 
             if self.flag_val:
-                Vx_val[:, n] = self.kernel.eval(X_val, X[indI_[n], :])[:, 0]\
-                    - Vx_val_new_X_old_ctrs[:, :] @ Vx_new_X_old_ctrs[indI_[n], :].transpose()\
-                    - Vx_val[:, :n+1] @ Vx[indI_[n], 0:n+1].transpose()
+                Vx_val[:, n] = self.kernel.eval(X_val, X[indI_[n], :])[:, 0] \
+                               - Vx_val_new_X_old_ctrs[:, :] @ Vx_new_X_old_ctrs[indI_[n], :].transpose() \
+                               - Vx_val[:, :n + 1] @ Vx[indI_[n], 0:n + 1].transpose()
                 Vx_val[:, n] = Vx_val[:, n] / np.sqrt(p[indI_[n]])
 
-
             # update the change of basis
             Cut_new_row = np.ones(N_ctrs_so_far + n + 1)
             Cut_new_row[:N_ctrs_so_far + n] = \
@@ -288,16 +279,14 @@ class VKOGA_2L(BaseEstimator):
             self.print_message('end')
 
         # Define coefficients and centers
-        self.c =  np.concatenate((self.c, c[:n + 1]))
+        self.c = np.concatenate((self.c, c[:n + 1]))
         self.Cut_ = Cut_[:N_ctrs_so_far + n + 1, :N_ctrs_so_far + n + 1]
-        self.indI_ = indI_[:n + 1]     # Mind: These are only the indices of the latest points
+        self.indI_ = indI_[:n + 1]  # Mind: These are only the indices of the latest points
         self.coef_ = self.Cut_.transpose() @ self.c
         self.ctrs_ = np.concatenate((self.ctrs_, X[self.indI_, :]), axis=0)
 
-
         return self
 
-
     def predict(self, X):
         # Check is fit has been called
         # check_is_fitted(self, 'coef_')     # ToDo: Remove this one!
@@ -352,10 +341,11 @@ class VKOGA_2L(BaseEstimator):
             print('       |_ regularization par. : %2.2e' % self.reg_par)
             print('       |_ restriction par.    : %2.2e' % self.restr_par)
             print('')
-            
+
         if self.verbose and when == 'end':
             print('Training completed with')
-            print('       |_ selected points     : %8d / %8d' % (self.train_hist['n'][-1], self.ctrs_.shape[0] + self.maxIter))
+            print('       |_ selected points     : %8d / %8d' % (
+            self.train_hist['n'][-1], self.ctrs_.shape[0] + self.maxIter))
             if self.flag_val:
                 print('       |_ train, val residual : %2.2e / %2.2e,    %2.2e' %
                       (self.train_hist['f'][-1], self.tol_f, self.train_hist['f val'][-1]))
@@ -364,10 +354,11 @@ class VKOGA_2L(BaseEstimator):
             else:
                 print('       |_ train residual      : %2.2e / %2.2e' % (self.train_hist['f'][-1], self.tol_f))
                 print('       |_ train power fun     : %2.2e / %2.2e' % (self.train_hist['p'][-1], self.tol_p))
-                        
+
         if self.verbose and when == 'track':
             print('Training ongoing with')
-            print('       |_ selected points     : %8d / %8d' % (self.train_hist['n'][-1], self.ctrs_.shape[0] + self.maxIter))
+            print('       |_ selected points     : %8d / %8d' % (
+            self.train_hist['n'][-1], self.ctrs_.shape[0] + self.maxIter))
             if self.flag_val:
                 print('       |_ train, val residual : %2.2e / %2.2e,    %2.2e' %
                       (self.train_hist['f'][-1], self.tol_f, self.train_hist['f val'][-1]))
@@ -378,17 +369,16 @@ class VKOGA_2L(BaseEstimator):
                 print('       |_ train power fun     : %2.2e / %2.2e' % (self.train_hist['p'][-1], self.tol_p))
 
 
-
-#%% Utilities to 
+# %% Utilities to
 import pickle
+
+
 def save_object(obj, filename):
     with open(filename, 'wb') as output:
         pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)
 
-def load_object(filename):        
+
+def load_object(filename):
     with open(filename, 'rb') as input:
-        obj = pickle.load(input)    
+        obj = pickle.load(input)
     return obj
-
-
-
-- 
GitLab