'/
FEEDFORWARD STEP (i.e. SIMULATION) FOR ANnet FUNCTION

This script contains the feedforward procedure to be used by the 
  ANnet function.

Specifications:
fSim() --> Function: returns the output matrix of a FeedForward step for
                     the ANnet function
ANnet  --> An ANnet object created by an ANnet function
X      --> The input matrix to be processed
Wh     --> The weight values for the input-hidden layer connection
Wo     --> The weight values for the hidden-output layer connection
WBh    --> The weight values for the hidden layer bias connection
WBo    --> The weight values for the output layer bias connection
fn     --> The activation function name
a      --> Parameter a (optional)
b      --> Parameter b (optional)

Author: Arthur Hrast Essenfelder
E-mail: arthur.essenfelder@unive.it

Version: 1.0
Last update on: 04/12/2015
/'



#Loading Functions -----------------------------------------------------
source("ANnet-ActivationFunctions.R");
source("ANnet-ModelTypeFunctions.R");
source("ANnet-NormalisationFunctions.R");
#-----------------------------------------------------------------------



#Simulate ANnet Object Function ========================================
fSim = function(ANnet.Object, Input.Data, Target.Data, 
                Model.Type.Variant, BiasFactor) {
  
  # Checking if ANnet.Object is an ANnet Object ------------------------
  if (class(ANnet.Object) != 'ANnet') {
    stop("The input must be an ANnet object!");
  }
  
  # Getting values from ANnet Object
  net                = ANnet.Object;
  Model.Type         = net@ModelType;
  d                  = net@TimeDelay;
  NormData           = net@NormData;
  NormMethod         = net@NormMethod;
  Xmax               = net@NormXmax;
  Xmin               = net@NormXmin;
  Xmean              = net@NormXmean;
  XstDev             = net@NormXstDev;
  Otmax              = net@NormOtmax;
  Otmin              = net@NormOtmin;
  Otmean             = net@NormOtmean;
  OtstDev            = net@NormOtstDev;
  alpha              = net@alpha;
  strActFun          = net@ActFunctionName;
  a                  = net@ActFunctionPrmA;
  b                  = net@ActFunctionPrmB;
  
  # Getting the weights
  if        (length(net@ActFunctionName) == 2) { #1-Hidden-Layered ANnet
    Wh   = net@Wh;
    Wo   = net@Wo;
    WBh  = net@WBh;
    WBo  = net@WBo;
  } else if (length(net@ActFunctionName) == 3) { #2-Hidden-Layered ANnet
    Wh1  = net@Wh1;
    Wh2  = net@Wh2;
    Wo   = net@Wo;
    WBh1 = net@WBh1;
    WBh2 = net@WBh2;
    WBo  = net@WBo;
  }
  # --------------------------------------------------------------------
  
  # Getting variables for the simulation -------------------------------
  X = Input.Data;
  j = ncol(Wo);
  k = nrow(X);
  if (missing(Model.Type.Variant)) { 
    Model.Type.Variant = net@ModelTypeVariant; 
  }
  if (missing(BiasFactor)) { 
    BiasFactor = net@BiasFactor; 
  }
  
  # Checking if Target.Data is missing
  if (missing(Target.Data)) {
    if (Model.Type != "Input-Output") {
      if (Model.Type == "NAR" || Model.Type == "NARX") {
        stop("Missing Target.Data");
      } else if (Model.Type == "Delayed Input-Output") {
        Ot = matrix(0., nrow=k, ncol=j);
      } else if (Model.Type.Variant == "Close-Loop") {
        stop("Missing Target.Data");
      } else { Ot = Target.Data; }
    }
  }
  
  # Checking if Target.Data is correctly passed to the function
  if (Model.Type.Variant == "Close-Loop") {
    if (nrow(Ot) != d) { stop("Invalid Target.Data"); }
  } else {
    if (Model.Type == "NAR" || Model.Type == "NARX") {
      if (nrow(Ot) != k) { stop("Invalid Target.Data"); }
    }
  }
  
  # Pre-processing the input data, if necessary
  if (Model.Type != "Input-Output") {
    if (Model.Type.Variant == "Open-Loop") {
      X = fTimeDelayData(X, Ot, Model.Type, d);
      Ot = as.matrix(Ot[(d+1):(k),]);
      i  = ncol(X);
      k  = nrow(X);
    } else if (Model.Type.Variant == "Close-Loop") {
      Ottmp = matrix(0., nrow=k, ncol=ncol(Ot));
      Ottmp[1:d,] = Ot;
      Ot = Ottmp;
      rm(Ottmp);
      X = fTimeDelayData(X, Ot, Model.Type, d);
      Ot = as.matrix(Ot[(d+1):(k),]);
      i  = ncol(X);
      k  = nrow(X);
    }
  }
  
  # Getting useful information on the structure of the ANnet Object
  if        (length(net@ActFunctionName) == 2) { #1-Hidden-Layered ANnet
    i = nrow(Wh);
    h = ncol(Wh);
    if (i != ncol(X)) { stop("Incorrect nrow Input.Data")}
  } else if (length(net@ActFunctionName) == 3) { #2-Hidden-Layered ANnet
    i  = nrow(Wh1);
    h1 = ncol(Wh1);
    h2 = ncol(Wh2);
    if (i != ncol(X)) { stop("Incorrect nrow Input.Data")}
  }
  
  # Normalise the data, if necessary
  if (NormData == TRUE) {
    X = norm(X, NormMethod, Xmax, Xmin, Xmean, XstDev);
  }
  # --------------------------------------------------------------------
  
  # Feedforward the data -----------------------------------------------
  if        (length(net@ActFunctionName) == 2) { #1-Hidden-Layered ANnet
    
    # Calculate the bias
    Bh = matrix(runif(k, 0., 1.)*BiasFactor, k, 1);
    Bo = matrix(runif(k, 0., 1.)*BiasFactor, k, 1);
    
    # Close-Loop data
    if (Model.Type.Variant == "Close-Loop") {
      id = matrix(1:k, nrow=k, ncol=1);
      X = fCloseLoopData(id, X, Model.Type, d, 
                         strActFun, a, b,
                         Wo, WBo, Bo, Wh, WBh, Bh);
    }
    
    # Feedforward
    # Hidden Layer
    Ah  = X  %*% Wh + Bh %*% t(WBh);
    Yh  = f(Ah, strActFun[1], a[1], b[1]);
    # Output Layer
    Ao  = Yh %*% Wo + Bo %*% t(WBo);
    O   = f(Ao, strActFun[2], a[2], b[2]);
    
  } else if (length(net@ActFunctionName) == 3) { #2-Hidden-Layered ANnet
    
    # Calculate the bias
    Bh1 = matrix(runif(k, 0., 1.)*BiasFactor, k, 1);
    Bh2 = matrix(runif(k, 0., 1.)*BiasFactor, k, 1);
    Bo  = matrix(runif(k, 0., 1.)*BiasFactor, k, 1);
    
    # Close-Loop data
    if (Model.Type.Variant == "Close-Loop") {
      id = matrix(1:k, nrow=k, ncol=1);
      X = fCloseLoopData(id, X, Model.Type, d, 
                         strActFun, a, b,
                         Wo, WBo, Bo, Wh2, WBh2, Bh2, 
                         Wh1, WBh1, Bh1);
    }
    
    # Feedforward
    # Hidden Layer 1
    Ah1 = X   %*% Wh1 + Bh1 %*% t(WBh1);
    Yh1 = f(Ah1, strActFun[1], a[1], b[1]);
    # Hidden Layer 2
    Ah2 = Yh1 %*% Wh2 + Bh2 %*% t(WBh2);
    Yh2 = f(Ah2, strActFun[2], a[2], b[2]);
    # Output Layer
    Ao  = Yh2 %*% Wo  + Bo  %*% t(WBo);
    O   = f(Ao,  strActFun[3], a[3], b[3]);
    
  }
  # --------------------------------------------------------------------
  
  # Finilising the Simulation ------------------------------------------
  # Removing the normalisation, if necessary
  if (NormData == TRUE) {
    O = norm.inv(O, NormMethod, Otmax, Otmin, Otmean, OtstDev);
  }
  
  # Returning simulation
  return(O);
  # --------------------------------------------------------------------
  
}
# ======================================================================