
import numpy as np
np.random.seed(42)

class ELM(object):  
    
  def __init__(self, inputSize, outputSize, hiddenSize):
    """
    Initialize weight and bias between input layer and hidden layer
    Parameters:
    inputSize: int
        The number of input layer dimensions or features in the training data
    outputSize: int
        The number of output layer dimensions
    hiddenSize: int
        The number of hidden layer dimensions        
    """    

    self.inputSize = inputSize
    self.outputSize = outputSize
    self.hiddenSize = hiddenSize       
    
    # Initialize random weight with range [-0.5, 0.5]
    self.weight = np.matrix(np.random.uniform(-0.5, 0.5, (self.hiddenSize, self.inputSize)))

    # Initialize random bias with range [0, 1]
    self.bias = np.matrix(np.random.uniform(0, 1, (1, self.hiddenSize)))
    
    self.H = 0
    self.beta = 0

  def sigmoid(self, x):
    """
    Sigmoid activation function
    
    Parameters:
    x: array-like or matrix
        The value that the activation output will look for
    Returns:      
        The results of activation using sigmoid function
    """
    return 1 / (1 + np.exp(-1 * x))

  def predict(self, X):
    """
    Predict the results of the training process using test data
    Parameters:
    X: array-like or matrix
        Test data that will be used to determine output using ELM
    Returns:
        Predicted results or outputs from test data
    """
    X = np.matrix(X)
    y = self.sigmoid((X * self.weight.T) + self.bias) * self.beta

    return y

  def train(self, X, y):
    """
    Extreme Learning Machine training process
    Parameters:
    X: array-like or matrix
        Training data that contains the value of each feature
    y: array-like or matrix
        Training data that contains the value of the target (class)
    Returns:
        The results of the training process   
    """

    X = np.matrix(X)
    y = np.matrix(y)        
    
    # Calculate hidden layer output matrix (Hinit)
    self.H = (X * self.weight.T) + self.bias

    # Sigmoid activation function
    self.H = self.sigmoid(self.H)

    # Calculate the Moore-Penrose pseudoinverse matriks        
    H_moore_penrose = np.linalg.inv(self.H.T * self.H) * self.H.T

    # Calculate the output weight matrix beta
    self.beta = H_moore_penrose * y

    return self.H * self.beta