/* *************************************************************   
 
        Final Project                                 MMU 1996

        ###################################################### 
        #                                                    #  
        #               A Modular Neural Network             #     
        #                                                    # 
        #                THE SINGLE LAYER CLASS              #  
        #                                                    #  
        ######################################################  
    
        Albrecht Schmidt                              09.08.96    
     
        FILE: CLayer.C                             Version 1.0 
   
   ************************************************************* */  
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "global.h"
#include "CCell.h"
#include "CLayer.h"

// #define DEBUG 1
// #define PRINT 1

#define PRG_NAME        "\nCLayer"

/* ---------------------------------------------------------------------
        The Implementation of the constructor/destructor
-------------------------------------------------------------------- */
// Constructor with arguments
CLayer::CLayer(int no_in, int no_out, TFunction fct, double lam)
{
	#if DEBUG
	   cout << PRG_NAME << "::CLayer(...) Constructor parameter";
	#endif

	int i;
        // check if the number of neurons is ok
	if (no_out >= MAX_NEURONS)
	{
	    cout << ERR_MSG << " to many Neurons! - SORRY :-(\n";
	    exit (-1);
	}

	// get the parameters in the class variable
	no_inputs  = no_in + 1;         // Bias => +1
	no_outputs = no_out;
	function   = fct;

	lambda       = lam;
	const_input  = -1;

 

	TVector tempV = new double[no_inputs];

	// generate the neurons an initialize them
	for(i=0;i<no_outputs;i++)
	{
		neuron[i] = new CCell(no_inputs, function);
		neuron[i]->SetWeights(RandomV(tempV, no_inputs, MIN_RND, MAX_RND));
	}
	SetLambda(lambda);
	delete[] tempV;
}

// Destructor
CLayer::~CLayer()
{
        #if DEBUG 
	   cout << PRG_NAME << "::~CLayer() Destructor";
        #endif 

        int i;
        for(i=0;i<no_outputs;i++)
        {        
		neuron[i]->CCell::~CCell();
        }
}

/* ---------------------------------------------------------------------
        The Implementation of further functions 
-------------------------------------------------------------------- */
// enlarge the Vector to add the BIAS
TVector CLayer::AddConst(TVector retV, TVector inputV)
{
	#if DEBUG
		cout << PRG_NAME << "::AddConst(...)";
	#endif

	int i;

	// copy the values
	for(i=0;i<no_inputs - 1;i++)
		retV[i] = inputV[i];

	// add the bias
	retV[no_inputs - 1] = const_input;

	return retV;
}

/* ---------------------------------------------------------------------
                The Implementation of the accessing functions
-------------------------------------------------------------------- */
// Set the labda value for the layer
void CLayer::SetLambda(double lam)
{        
	#if DEBUG
                cout << PRG_NAME << "::SetLambda(...)";
        #endif
	lambda = lam;

	int i;
	for(i=0;i<no_outputs;i++)
        {
                neuron[i]->SetLambda(lambda);
        }

}     

// Get the labda value for the layer
double CLayer::GetLambda()
{        
        #if DEBUG
		cout << PRG_NAME << "::GetLambda()";
        #endif
        return lambda;
}
 
// function to get the number of Outputs
int CLayer::GetNoOutputs()
{
        #if DEBUG
                cout << PRG_NAME << "::GetNoOutputs()";
        #endif
        return no_outputs;
}      

// function to get the number of inputs
int CLayer::GetNoInputs()
{
        #if DEBUG
                cout << PRG_NAME << "::GetNoInputs()";
        #endif
	return (no_inputs-1);
}      

/* ---------------------------------------------------------------------
                The Implementation of the calculations
-------------------------------------------------------------------- */
// Calculate the Net Vector (weighted sum of inputs)
TVector CLayer::Net(TVector retV, TVector inputV)
{
	#if DEBUG
		cout << PRG_NAME << "::Net(...)";
	#endif

	int i;
	TVector tempV;
	tempV = new double[no_inputs];

	// get the net value for each neuron and put it in a Vector
	for(i=0;i<no_outputs;i++)
	{
		retV[i] = neuron[i]->Net(AddConst(tempV, inputV));
	}
	delete[] tempV;

	return retV;
}

// Calculate the Output Vector ( f(net) )
TVector CLayer::Out(TVector retV, TVector inputV)
{
	#if DEBUG
		cout << PRG_NAME << "::Out(...)";
	#endif

	int i;
	TVector tempV;
	tempV = new double[no_inputs];


	// get the out value for each neuron and put it in a Vector
	for(i=0;i<no_outputs;i++)
	{
		retV[i] = neuron[i]->Out(AddConst(tempV, inputV));
	}
	delete[] tempV;
	return retV;
}

double CLayer::Derive(double net)
{
	double f1d;

	#ifdef DEBUG
	   cout << PRG_NAME << "::Derive(...) \n";
	#endif

	// apply the function which is specified to net

	switch( function )
	{
	  case bi_pol:
		{
		  f1d = (2 * lambda * exp(-lambda * net)) /
				pow(1+exp(-lambda * net),2);
		  #ifdef DEBUG
		     cout << "\nFunction: (bi_pol)' \n";
		  #endif
		  break;
		}
	  case uni_pol:
		{
		  f1d = lambda * exp(-lambda * net) /
				pow(1+exp(-lambda * net),2);
		  #ifdef DEBUG
		     cout << "\nFunction: (uni_pol)' \n";
		  #endif
		  break;
		}
	  default:
		{
		  cerr << "\n\nWarning: No  f'(net) value derived !";
		  cerr << "\n         wrong function specified !!!";
		  f1d = net;
		}
	}

	// return f'(net)
	#ifdef PRINT
		cout << "\n f'(" << net << ") = " << f1d << "\n";
	#endif
	return f1d;
}

// Calculate the output layer delta
TVector CLayer::DeltaOut(TVector retV, TVector inputV, TVector targetV)
{
	// better ?? parameter outputV ???
	#if DEBUG
		cout << PRG_NAME << "::DeltaOut(...)";
	#endif

	double net, out, f1d;
	int i;
	TVector tempV;
	tempV = new double[no_inputs];

	// calculate for each neuron net, out and derivtive
	// Errorsignal:= (target - out) * f'(net)
	for(i=0;i<no_outputs;i++)
	{
		net =  neuron[i]->Net(AddConst(tempV, inputV));
		out =  neuron[i]->Out(AddConst(tempV, inputV));
		f1d =  Derive(net);

		retV[i] = (targetV[i] - out) * f1d;
	}

	delete[] tempV;
	return retV;
}

// Calculate the hidden layer delta
TVector CLayer::DeltaHidden(TVector retV, TVector netV, TVector deltaV)
{
	#if DEBUG
		cout << PRG_NAME << "::DeltaHidden(...)";
	#endif

	double sum, f1d;
	int i, j;
	TVector weightV;
	weightV = new double[no_inputs];

	// calculate for each input
	// Errorsignal:= f'(net) * sum(delta(j)*w(i,j))
	// 0 <= i < no_inputs, 0<= j < no_outputs, net from previous layer
	for(i=0;i<no_inputs-1;i++)
	{
		f1d =  Derive(netV[i]);
		sum = 0;
		for(j=0;j<no_outputs;j++)
		{
			weightV = neuron[j]->GetWeights(weightV);
			#if PRINT
				cout << "\nw[" << j << "," << i << "] " << weightV[i];
				cout << "\nd["<< j << "] " << deltaV[j];
				cout << "\nw[" << j << "," << i << "] * d[";
				cout << j << "] = " << weightV[i] * deltaV[j];
			#endif
			sum = sum + weightV[i] * deltaV[j];
		}

		retV[i] = f1d * sum;
		#if PRINT
			cout << "\n sum = " << sum;
			cout << "\n f1d = " << f1d;
			cout << "\n ret = " << retV[i];
		#endif

	}

        // free the memory
	delete[] weightV;
	return retV;
}

// update the weights
void CLayer::UpdateWeights(double eta, 		// learning constant
                           TVector inputV, 	// input vector
		           TVector deltaV,	// delta vector
                           double alpha)		// momentum alpha
{
	#if DEBUG
		cout << PRG_NAME << "::UpdateWeights(...)";
	#endif

	int i,j;
	double deltaW;
	TVector deltaOldV, weightV, inputVBias;
    
        // allocate memory
	deltaOldV = new double[no_inputs];
	weightV = new double[no_inputs];
	inputVBias = new double[no_inputs];

        // enlarge the input vector with the bias
	inputVBias = AddConst(inputVBias, inputV);

        // for each neuron do
	for(i=0;i<no_outputs;i++)
	{
                // get the old elta weights
		deltaOldV = neuron[i]->GetDeltaWeights(deltaOldV);
                // get the new weights
		weightV = neuron[i]->GetWeights(weightV);
		#if PRINT
			PrintV(weightV, no_inputs, "BeforeUpdate");
		#endif
                // update according to the backprop algorithm
		for(j=0;j<no_inputs;j++)
		{
			deltaW = eta * deltaV[i] * inputVBias[j];
			weightV[j] = weightV[j] + deltaW +
				     alpha * deltaOldV[j];
			deltaOldV[j] = deltaW;
		}
                // set the weights in the cell
		neuron[i]->SetWeights(weightV);
		neuron[i]->SetDeltaWeights(deltaOldV);
	}

         // free the memory
	delete[] deltaOldV;
	delete[] weightV;
	delete[] inputVBias;
}


// Display the Weights
void CLayer::PrintWeights()
{
	#if DEBUG
		cout << PRG_NAME << "::PrintWeights(...)";
	#endif

	int i;
	TVector tempV = new double[no_inputs];

	// Print the weights for each neuron
	for(i=0;i<no_outputs;i++)
	{
		PrintV(neuron[i]->GetWeights(tempV), no_inputs, "weight");
	}
	delete[] tempV;
}

// Write the Weights in a file
void CLayer::WriteWeights(FILE* fd)
{
	#if DEBUG
		cout << PRG_NAME << "::WriteWeights(...)";
	#endif

	TVector weightV;
	weightV = new double[no_inputs];
	int i, j;

	fprintf(fd, "\n");
	// Print the weights for each neuron
	for(i=0;i<no_outputs;i++)
	{
		weightV = neuron[i]->GetWeights(weightV);
		for(j=0;j<no_inputs;j++)
			fprintf(fd, "%f ",  weightV[j]);
		fprintf(fd, ";\n");
	}
	delete[] weightV;
}

// Read the Weights from file
void CLayer::ReadWeights(FILE* fd)
{
	#if DEBUG
		cout << PRG_NAME << "::WriteWeights(...)";
	#endif

	char sep[20];
	TVector weightV;
	weightV = new double[no_inputs];
	int i, j, no;

	// Read the weights for each neuron
	for(i=0;i<no_outputs;i++)
	{
		for(j=0;j<no_inputs;j++)
		{
		       no = fscanf(fd, "%lf",  &weightV[j]);
		       if (no != 1)
		       {
			   cout << WARN_MSG << " wrong weight file !!!\n";
			   cout << "No of weights per neuron violated !!!\n";
		       }
		}
		fscanf(fd, "%1s\n", sep);
		if (sep[0] != ';')
		{
		   cout << WARN_MSG << " wrong weight file !!!\n";
		   cout << "Seperator no present !!!\n";
		}
		neuron[i]->SetWeights(weightV);
	}
	delete[] weightV;
}


// Set the weights to a random value
void CLayer::ResetWeights(double min, double max)
{
	#if DEBUG
		cout << PRG_NAME << "::ResetWeights(...)";
	#endif

	int i;

	TVector tempV = new double[no_inputs];

	// set the weights for each neuron by random
	for(i=0;i<no_outputs;i++)
	{
		neuron[i]->SetWeights(RandomV(tempV, no_inputs, min, max));
	}
	delete[] tempV;
}

