// Copyright (C) 2001 Jean-Marc Valin

#include "FFNet.h"

#include <vector>
#include "ObjectParser.h"

#ifdef HAVE_FLOAT_H
#include <float.h>
#endif

#include "misc.h"
#include "Array.h"

using namespace std;

namespace FD {

DECLARE_TYPE(FFNet)
//DECLARE_TYPE(Vector<FFNet>)
DECLARE_TYPE(Vector<FFNet*>)
DECLARE_TYPE2("Vector<RCPtr<FFNet>>", Vector<RCPtr<FFNet> >)

//@implements FFNet

FFNet::FFNet(const Vector<int> &_topo, const Vector<string> &functions)
   : topo(_topo)
   , layers(topo.size()-1)
{
   init(functions);
}


void FFNet::init(const Vector<string> &functions)
{
   nbNeurons = 0;
   nbWeights = 0;
   for (int i=0;i<topo.size()-1;i++)
   {
      nbWeights += (topo[i]+1)*topo[i+1];
      nbNeurons += topo[i+1];
   }
   weights = new float [nbWeights];

   int weightOffset = 0;
   int neuronOffset = 0;
   for (int i=0;i<topo.size()-1;i++)
   {
      layers[i]=new FFLayer(topo[i+1], topo[i], weights, weightOffset, neuronOffset, functions[i]);
      //layers[i]->init(1.0);
      weightOffset += (topo[i]+1)*topo[i+1];
      neuronOffset += topo[i+1];
   }
}


FFNet::FFNet(const Vector<int> &_topo, const Vector<string> &functions, vector<float *> &tin, vector<float *> &tout)
   : topo(_topo)
   , layers(topo.size()-1)
{
   init(functions);

   //cerr << tin.size() << endl;
   vector<double> inputMeans(topo[0], 0);
   vector<double> outputMeans(topo[topo.size()-1], 0);
   vector<double> inputStd(topo[0], 0);
   vector<double> outputStd(topo[topo.size()-1], 0);
   
   for (int i=0;i<tin.size();i++)
      for (int j=0;j<topo[0];j++)
	 inputMeans[j] += tin[i][j];


   for (int j=0;j<topo[0];j++)
      inputMeans[j] /= tin.size();

   for (int i=0;i<tin.size();i++)
      for (int j=0;j<topo[0];j++)
	 inputStd[j] += sqr(tin[i][j]-inputMeans[j]);


   for (int j=0;j<topo[0];j++)
       inputStd[j] = sqrt(inputStd[j]/tin.size());
   
   for (int i=0;i<tout.size();i++)
      for (int j=0;j<topo[topo.size()-1];j++)
	 outputMeans[j] += tout[i][j];

   for (int j=0;j<topo[topo.size()-1];j++)
      outputMeans[j] /= tout.size();

   for (int i=0;i<tout.size();i++)
      for (int j=0;j<topo[topo.size()-1];j++)
	 outputStd[j] += (tout[i][j]-outputMeans[j]);
   
   //cerr << endl;
   for (int j=0;j<topo[topo.size()-1];j++)
      outputStd[j] = sqrt(outputStd[j]/tout.size());
   
   
   for (int i=0;i<topo.size()-1;i++)
   {
      //layers[i]=new FFLayer(topo[i+1],topo[i], functions[i]);
      if (i==0)
      {
	 layers[i]->init(&inputMeans[0], &inputStd[0]);
	 //layers[i]->init(10);
      } else { 
	 //layers[i]->init(10.0);
	 layers[i]->init(1.0);
      }
      if (i==topo.size()-2)
	 layers[i]->setBias(&outputMeans[0]);
   }
}

FFNet::FFNet(FFNet &net)
   : topo(net.topo)
   , layers(net.layers.size())
{
   cerr << "I wouldn't do that if I were you...\n";
//   for (int i=0;i<layers.size();i++)
//      layers[i] = new FFLayer(*(net.layers[i]));
}

void FFNet::setupLayersAfterRead()
{
   nbNeurons = 0;
   nbWeights = 0;
   for (int i=0;i<topo.size()-1;i++)
   {
      nbWeights += (topo[i]+1)*topo[i+1];
      nbNeurons += topo[i+1];
   }
   weights = new float [nbWeights];
   
   int weightOffset = 0;
   int neuronOffset = 0;
   for (int i=0;i<topo.size()-1;i++)
   {
      //layers[i]=new FFLayer(topo[i+1], topo[i], weights, weightOffset, neuronOffset, functions[i]);
      //layers[i]->init(1.0);
      layers[i]->setupAfterRead(weights, weightOffset, neuronOffset);
      weightOffset += (topo[i]+1)*topo[i+1];
      neuronOffset += topo[i+1];
   }

}

void FFNet::learn(float *input, float *output, double *gradient, double *err, float *calc_output)
{
   int outputLayer = topo.size()-2;

   //float value[nbNeurons];
   //float deriv[nbNeurons];
   //float error[nbNeurons];
   //float fgradient[nbWeights];
   DYN_VEC(float, nbNeurons, value);
   DYN_VEC(float, nbNeurons, deriv);
   DYN_VEC(float, nbNeurons, error);
   DYN_VEC(float, nbWeights, fgradient);

   float *calc_out = calc(input, value, deriv);

   if (calc_output)
      for (int i=0;i<topo[topo.size()-1];i++)
	 calc_output[i]=calc_out[i];

   if (err)
   {
      *err += vec_dist2(calc_out,output, topo[topo.size()-1]);
      //cout << calc_out[0] << " " << output[0] << " " << vec_dist2(calc_out,output, topo[topo.size()-1]) << endl;
   }
   
   //start with the output layer, towards the input
   for (int k=outputLayer;k>=0;k--)
   {
      FFLayer *currentLayer = &(*layers[k]);
      
      float *previousValue, *currentValue;
      if (k==0)
	 previousValue = input;
      else 
	 previousValue = value + layers[k-1]->getNeuronOffset();

      currentValue = value + currentLayer->getNeuronOffset();
      int layerSize = topo[k+1];
      int layerInputs = topo[k];
      float *delta = error + currentLayer->getNeuronOffset();

      if (k==outputLayer)
      {
	 // Error calculation is simple

	 for (int i=0;i<layerSize;i++)
	    delta[i]=output[i]-currentValue[i];
      } else {
	 // Perform error backpropagation

	 for (int i=0;i<layerSize;i++)
	    delta[i] = 0;
	 float *outErrPtr = error + layers[k+1]->getNeuronOffset();
	 for (int j=0;j<topo[k+2];j++)
	 {
	    float *outW = layers[k+1]->getWeights(j);
	    float outErr = outErrPtr[j];
	    vec_mul_and_add(outErr, outW, delta, layerSize);
	 }
      }

      for (int i=0;i<layerSize;i++)
      {
	 float *grad = fgradient + currentLayer->getNeuronWeightOffset(i);
	 delta[i] = deriv[i+currentLayer->getNeuronOffset()]*delta[i];
	 
	 vec_mul_scal (delta[i], previousValue, grad, layerInputs);
	 grad[layerInputs] = delta[i];
      }
      
      
   }

   for (int i=0;i<nbWeights;i++)
      gradient[i] += fgradient[i];
}


void FFNet::calcGradient(vector<float *> &tin, vector<float *> &tout, Array<float> eval_weights, Array<double> &gradient, double &err)
{
   int i,j;

   //float tmp[nbWeights];
   DYN_VEC(float, nbWeights, tmp);

   for (int i=0;i<nbWeights;i++)
   {
      tmp[i] = weights[i];
      weights[i] = eval_weights[i];
   }
   //float *tmp = weights;
   //weights = &eval_weights[0];
   
   err=0;
   for (i=0;i<nbWeights;i++)
      gradient[i] = 0;

   for (i=0;i<tin.size();i++)
   {
      learn (tin[i], tout[i], &gradient[0], &err);
   }

   //getGradient(&gradient[0]);
   gradient = -gradient;
   //cerr << gradient[0] << endl;
   //vec_prod_scalar(gradient, -1.0, gradient, );

   for (int i=0;i<nbWeights;i++)
      weights[i] = tmp[i];

   //weights = tmp;
}


void FFNet::weightedLearn(float *input, float *output, float *learnWeights, double *gradient, double *err, float *calc_output)
{
   int outputLayer = topo.size()-2;

   //float value[nbNeurons];
   //float deriv[nbNeurons];
   //float error[nbNeurons];
   //float fgradient[nbWeights];
   DYN_VEC(float, nbNeurons, value);
   DYN_VEC(float, nbNeurons, deriv);
   DYN_VEC(float, nbNeurons, error);
   DYN_VEC(float, nbWeights, fgradient);
   
   float *calc_out = calc(input, value, deriv);

   if (calc_output)
      for (int i=0;i<topo[topo.size()-1];i++)
	 calc_output[i]=calc_out[i];

   if (err)
   {
      for (int i=0;i<topo[topo.size()-1];i++)
	 *err += sqr(calc_out[i]-output[i])*learnWeights[i];
      //*err += vec_dist2(calc_out,output, topo[topo.size()-1]);
      //cout << calc_out[0] << " " << output[0] << " " << vec_dist2(calc_out,output, topo[topo.size()-1]) << endl;
   }
   
   //start with the output layer, towards the input
   for (int k=outputLayer;k>=0;k--)
   {
      FFLayer *currentLayer = &(*layers[k]);
      
      float *previousValue, *currentValue;
      if (k==0)
	 previousValue = input;
      else 
	 previousValue = value + layers[k-1]->getNeuronOffset();

      currentValue = value + currentLayer->getNeuronOffset();
      int layerSize = topo[k+1];
      int layerInputs = topo[k];
      float *delta = error + currentLayer->getNeuronOffset();

      if (k==outputLayer)
      {
	 // Error calculation is simple

	 for (int i=0;i<layerSize;i++)
	    delta[i]=(output[i]-currentValue[i])*learnWeights[i];
      } else {
	 // Perform error backpropagation

	 for (int i=0;i<layerSize;i++)
	    delta[i] = 0;
	 float *outErrPtr = error + layers[k+1]->getNeuronOffset();
	 for (int j=0;j<topo[k+2];j++)
	 {
	    float *outW = layers[k+1]->getWeights(j);
	    float outErr = outErrPtr[j];
	    vec_mul_and_add(outErr, outW, delta, layerSize);
	 }
      }

      for (int i=0;i<layerSize;i++)
      {
	 float *grad = fgradient + currentLayer->getNeuronWeightOffset(i);
	 delta[i] = deriv[i+currentLayer->getNeuronOffset()]*delta[i];
	 
	 vec_mul_scal (delta[i], previousValue, grad, layerInputs);
	 grad[layerInputs] = delta[i];
      }
      
      
   }

   for (int i=0;i<nbWeights;i++)
      gradient[i] += fgradient[i];
}


void FFNet::weightedCalcGradient(vector<float *> &tin, vector<float *> &tout, vector<float *> &learnWeights, 
				 Array<float> eval_weights, Array<double> &gradient, double &err)
{
   int i,j;

   //float tmp[nbWeights];
   DYN_VEC(float, nbWeights, tmp);

   for (int i=0;i<nbWeights;i++)
   {
      tmp[i] = weights[i];
      weights[i] = eval_weights[i];
   }
   //float *tmp = weights;
   //weights = &eval_weights[0];
   
   err=0;
   for (i=0;i<nbWeights;i++)
      gradient[i] = 0;

   for (i=0;i<tin.size();i++)
   {
      weightedLearn (tin[i], tout[i], learnWeights[i], &gradient[0], &err);
   }

   //getGradient(&gradient[0]);
   gradient = -gradient;
   //cerr << gradient[0] << endl;
   //vec_prod_scalar(gradient, -1.0, gradient, );

   for (int i=0;i<nbWeights;i++)
      weights[i] = tmp[i];

   //weights = tmp;
}


float FFNet::totalError(vector<float *> tin, vector<float *> tout)
{

   double SSE=0;

   Array<float> wk(nbWeights);
   vec_copy(weights, &wk[0], nbWeights);
   //getWeights(&wk[0]);
   Array<double> dEk(nbWeights);
   calcGradient(tin, tout, wk, dEk, SSE);
   return SSE;
}





void FFNet::setDerivOffset(float d)
{
   for (int i=0;i<layers.size();i++)
      layers[i]->setDerivOffset(d);
}

void FFNet::printOn(ostream &out) const
{
   out << "<FFNet " << endl;
   out << "<topo " << topo << ">" << endl;
   out << "<layers " << layers << ">" << endl;

   out << ">\n";
}


void FFNet::readFrom (istream &in)
{
   string tag;
   //cerr << "FFNet::readFrom\n";
   while (1)
   {
      char ch;
      in >> ch;
      if (ch == '>') break;
      else if (ch != '<') 
       throw new ParsingException ("FFNet::readFrom : Parse error: '<' expected");
      in >> tag;
      if (tag == "topo")
         in >> topo;
      else if (tag == "layers")
      {
         in >> layers;
      }
      else
         throw new ParsingException ("FFNet::readFrom : unknown argument: " + tag);
      if (!in) throw new ParsingException ("FFNet::readFrom : Parse error trying to build " + tag);
      in >> tag;
      if (tag != ">") 
         throw new ParsingException ("FFNet::readFrom : Parse error: '>' expected ");
   }
   setupLayersAfterRead();
}

istream &operator >> (istream &in, FFNet &net)
{
   if (!isValidType(in, "FFNet")) return in;
   net.readFrom(in);
   return in;
}
}//namespace FD


syntax highlighted by Code2HTML, v. 0.9.1