import numpy as np


class base_optimizer:
    """
    Base class to define the methods, which are to implemented
    """
    def __init__(self):
        pass
    
    def solve(self):
        raise NotImplementedError


class lstsqrs(base_optimizer):
    """
    Least squares optimizer.
    
    This optimizer pipes the training data to numpy.linalg.lstsq. This can be understood as the most simple optimizer. Use with strong caution because no regularization is applied, which poses the risk of overfitting and bad out-of-sample predictions
    """
    
    def __init__(self):
        pass
    
    def solve(self, feature_matrix, target_matrix):
        """
        Solver, which is invoked by derrom's fit method.
        
        Parameters:
        -----------
            feature_matrix : 2D numpy.ndarray
                Feature matrix with the feature vectors stored in the rows. The feature matrix is automatically generated by derrom's fit method
            target_matrix : 2D numpy.ndarray
                Target matrix with the target vectors stored in the rows. The target matrix is automatically generated by derrom's fit method
        """
        return np.asarray( np.linalg.lstsq(feature_matrix, target_matrix, rcond = -1)[0] )
      
      

class ridge(base_optimizer):
    """
    Regularized least squares optimizer. Rigde regression.
    
    This optimizer applies L2/Thikonov regularization to the regression weights. This approach counters overfitting and helps to improve out-of-sample predictions. It can be understood as sacrificing the models specificity to improve its ability to generalize. A balance between these two, i.e., an optimal alpha must be found to optimize the regression performance.
    
    Parameters:
    -----------
        alpha : float
            Regularization parameter. Larger values impose a stronger regularization. alpha = 0 eliminates the regularization und yields the least-squares estimator.
    """
    def __init__(self, alpha = 1e-6):
        self.alpha = alpha
    
    def solve(self, feature_matrix, target_matrix):
        """
        Solver, which is invoked by derrom's fit method.
        
        Parameters:
        -----------
            feature_matrix : 2D numpy.ndarray
                Feature matrix with the feature vectors stored in the rows. The feature matrix is automatically generated by derrom's fit method
            target_matrix : 2D numpy.ndarray
                Target matrix with the target vectors stored in the rows. The target matrix is automatically generated by derrom's fit method
        """
        return np.linalg.inv(feature_matrix.T @ feature_matrix + self.alpha * np.identity(feature_matrix.shape[1])) @ feature_matrix.T @ target_matrix


  

  
  
  
