// 
// File name: neuro_bp.cpp
//
// Discription: Main
//
// Programmer: Vladimir P. Stasevich (SV)
//
// History: 
//    Created 03.12.1999 (SV)
//
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#include "TNeuro.h"

static float              MiuParm     = 0.0;
static float              NiuParm     = 0.1;
static float              Limit       = 0.000001;
static unsigned           dSigma      = 0;

float SetMiuParm(float Mi)
{
   float a;
   
   a       = MiuParm;
   MiuParm = Mi;
   
   return a;
}

float SetNiuParm(float Ni)
{
   float a;
   
   a       = NiuParm;
   NiuParm = Ni;
   
   return a;
}

float SetLimit(float Li)
{
   float a;
   
   a     = Limit;
   Limit = Li;
   
   return a;
}

unsigned SetDSigma(unsigned d)
{
   unsigned u;
   
   u      = dSigma;
   dSigma = d;
   
   return u;
}

void NeuronBP::_allocateNeuron
( 
   unsigned num_inputs 
)
{
   deltas=NULL;

   if(num_inputs==0) 
      return;

   deltas= new float[num_inputs];

   if( deltas==NULL ) 
      stat = STATE_ERROR;
   else 
      for( unsigned i=0;i<rang;i++ ) 
         deltas[i]=0.;
}

NeuronBP::NeuronBP
(
   unsigned num_inputs
) : NeuronFF( num_inputs )
{
   _allocateNeuron( num_inputs );
}

void NeuronBP::_deallocate( void )
{
   if( deltas && (stat == STATE_OK) )
   {
      delete [] deltas; 
      deltas=NULL;
   }
}

NeuronBP::~NeuronBP()
{
   _deallocate();
}

void NeuronBP::InitNeuron
(
   unsigned num_inputs
)
{
   NeuronFF::InitNeuron( num_inputs );

   if( deltas && (stat == STATE_OK) )
      delete [] deltas;

   _allocateNeuron( num_inputs );
}

float lastDiff, dlgDiff;

int NeuronBP::IsConverged( void )
{
   for( unsigned i=0; i<rang; i++ )
      if( fabs(deltas[i]) > Limit )
      {
//         if( lastDiff < fabs(deltas[i]) ) 
//            lastDiff = fabs(deltas[i]);
         return 0;
      }

   return 1;
}

//
LayerBP::LayerBP
(
   unsigned nRang, 
   unsigned nSynapses
)
{
   allocation = EXTERN; 
   stat       = STATE_ERROR;
   neuronrang = 0;
   
   if( nRang == 0 ) 
      return;
   
   neurons = new NeuronBP[nRang];
   
   if( neurons == NULL ) 
      return;
      
   for( unsigned i=0; i<nRang; i++ )
      neurons[i].InitNeuron( nSynapses );
      
   rang       = nRang;
   neuronrang = nSynapses;
   allocation = INNER;
   name       = NULL; 
   stat       = STATE_OK;
}

LayerBP::LayerBP
(
   NeuronBP  *Neu, 
   unsigned   nRang,
   unsigned   nSynapses
)
{
    neurons    = NULL; 
    neuronrang = 0; 
    allocation = EXTERN;
    
    for( unsigned i=0; i<nRang; i++ )
       if( Neu[i].rang != nSynapses ) 
         stat = STATE_ERROR;
         
    if( stat == STATE_OK )
    {
        neurons    = Neu;
        rang       = nRang;
        neuronrang = nSynapses;
    }
}

LayerBP::~LayerBP( void )
{
   if( allocation == INNER )
   {
      for( unsigned i=0; i<rang; i++ )
         neurons[i]._deallocate();
         
      delete [] neurons; 
      neurons = NULL;
   }
}

void LayerBP::Propagate( void )
{
    for( unsigned i=0; i<rang; i++ )
       neurons[i].Propagate();
}

void LayerBP::Update(void)
{
   for( unsigned i=0; i<rang; i++ )
   {
      for( unsigned j=0; j<neuronrang; j++ )
         neurons[i].synapses[j] -= neurons[i].deltas[j];
   }
}

void LayerBP::Randomize
(
   float range
)
{
   for( unsigned i=0; i<rang; i++ ) 
      neurons[i].Randomize( range );
}

void LayerBP::RandomizeAxons( void )
{
    for( unsigned i=0; i<rang; i++ )
       neurons[i].RandomizeAxon();
}

void LayerBP::Normalize( void )
{
   float    sum = 0;
   unsigned i;

   for( i=0; i<rang; i++ )
      sum += neurons[i].axon*neurons[i].axon;
      
   sum = sqrt(sum);
   for( i=0; i<rang; i++ ) 
      neurons[i].axon /= sum;
}

/*
void LayerBP::Show( void )
{
   unsigned char sym[5] = { GRAPHCHAR_EMPTYBLACK, 
                            GRAPHCHAR_DARKGRAY, 
                            GRAPHCHAR_MIDDLEGRAY,
                            GRAPHCHAR_LIGHTGRAY, 
                            GRAPHCHAR_SOLIDWHITE 
                          };

   int i, j;
   
   if( y && name ) 
      for( i=0; i<strlen(name); i++ )
         out_char( x+i, y-1, name[i], 3 );
         
   out_char( x, y, GRAPHCHAR_UPPERLEFTCORNER, 15 );
   
   for( i=0; i<2*dx; i++ )
      out_char(x+1+i,y,GRAPHCHAR_HORIZONTALLINE,15 );
      
   out_char( x+1+i, y, GRAPHCHAR_UPPERRIGHTCORNER, 15 );
   
   for( j=0; j<dy; j++ )
   {
      out_char( x, y+1+j, GRAPHCHAR_VERTICALLINE, 15 );
      
      for( i=0; i<2*dx; i++ ) 
         out_char( x+1+i, y+1+j, sym[(int)((neurons[j*dx+i/2].axon+0.4999)*5)], 15 );
         
      out_char( x+1+i, y+1+j, GRAPHCHAR_VERTICALLINE, 15 );
   }
   
   out_char( x, y+j+1, GRAPHCHAR_BOTTOMLEFTCORNER, 15 );
   
   for( i=0; i<2*dx; i++ )
      out_char( x+i+1, y+j+1, GRAPHCHAR_HORIZONTALLINE, 15 );
      
   out_char( x+1+i, y+j+1, GRAPHCHAR_BOTTOMRIGHTCORNER, 15 );
}

void LayerBP::PrintSynapses
( 
   int x, 
   int y
)
{
   for( unsigned i=0; i<rang; i++ )
      neurons[i].PrintSynapses( x, y+i );
}

void LayerBP::PrintAxons
(
   int x, 
   int y
)
{
   for( unsigned i=0; i<rang; i++ )
      neurons[i].PrintAxons( x, y+i );
}
*/

int LayerBP::IsConverged( void )
{
   for( unsigned i=0; i<rang; i++ )
      if( neurons[i].IsConverged() == 0 ) 
         return 0;
   
   return 1;
}

//
NetBP::NetBP
(
   unsigned nLayers
)
{
   layers = NULL;
   cycle  = 15; 

   if( nLayers == 0 ) 
   { 
      stat = STATE_ERROR;
      return; 
   }
   
   layers = new LayerBP *[nLayers];
   
   if( layers == NULL ) 
      stat = STATE_ERROR;
   else
   {
      rang=nLayers;
      for( unsigned i=0; i<rang; i++ ) 
         layers[i] = NULL;
   }
}

NetBP::~NetBP()
{
   if( rang )
   {
      for( unsigned i=0; i<rang; i++ ) 
         layers[i]->~LayerBP();
         
      delete [] layers; 
      layers = NULL;
   }
}

int NetBP::SetLayer
(
   unsigned  n,           // Номер устанавливаемого слоя
   LayerBP  *pl
)
{
   unsigned i,p;
    
   if( n >= rang ) 
      return 1;
    
   p = pl->rang;
    
   if( p == 0 ) 
      return 2;
    
   if( n ) // если не первый слой
   {
      if( layers[n-1] != NULL )     // если предыдущий слой уже подключен, про-
      {                             // веряем, равно ли число синапсов каждого
                                    // его нейрона числу нейронов предыд. слоя
         for( i=0; i<p; i++ )
            if( (*pl).neurons[i].rang != layers[n-1]->rang )
               return 3;
      }
   }
   
   if( n < rang-1 )                 // если не последний слой
   {
      if( layers[n+1] )
         for( i=0; i<layers[n+1]->rang; i++ )
            if( p != layers[n+1]->neurons[i].rang ) 
               return 4;
   }
   layers[n]=pl;
   
   return 0;
}

void NetBP::Propagate( void )
{
    for( unsigned i=1; i<rang; i++ )
       layers[i]->Propagate();
}

int NetBP::FullConnect( void )
{
   LayerBP *l;
   unsigned i, 
            j, 
            k, 
            n;          // Количество нейронов предыдущего слоя
   
   for( i=1; i<rang; i++ ) // кроме входного слоя
   {                       // по всем слоям
      l=layers[i];
      
      if( l->rang == 0 ) 
         return 1;
         
      n = (*layers[i-1]).rang;
      
      if( n == 0 ) 
         return 2;
         
      for( j=0; j<l->rang; j++ ) // по нейронам слоя
      {
         for( k=0; k<n; k++ )    // по синапсам нейрона
         {
            l->neurons[j].inputs[k] = &(layers[i-1]->neurons[k].axon);
         }
      }
   }
   
   return 0;
}

  // Установить входы сети - просто присваиваем выходам нулевого слоя нужные значения
void NetBP::SetNetInputs
(
   float *mv           // Массив чисел, которые будут входом нейросети
)
{
    for( unsigned i = 0; i < layers[0]->rang; i++ )
       layers[0]->neurons[i].axon = mv[i];
}

void NetBP::CalculateError
(
   float *Target
)
{
   NeuronBP *n;
   float     sum;
   unsigned  i;
   int       j;

     // Бежим по нейронам последнего слоя и вычисляем ошибку
   for( i=0; i<layers[rang-1]->rang; i++ )
   {
      n = &(layers[rang-1]->neurons[i]);
      n->error = (n->axon - Target[i]) * n->D_Sigmoid();

      if( lastDiff < n->error )
         lastDiff = n->error;

      if( dlgDiff < n->error )
         dlgDiff = n->error;
   }
   
   for( j = rang-2; j > 0; j-- ) // по скрытым слоям
   {
      for( i=0; i < layers[j]->rang; i++ ) // по нейронам
      {
         sum=0.;
         
         for( unsigned k = 0; k < layers[j+1]->rang; k++ )
            sum += layers[j+1]->neurons[k].error * 
                   layers[j+1]->neurons[k].synapses[i];
            
         layers[j]->neurons[i].error = sum * layers[j]->neurons[i].D_Sigmoid();
      }
   }
}

void NetBP::Learn( void )
{
      // По всем слоям, кроме входного
   for( int j=rang-1; j>0; j-- )
   {
         // по нейронам
      for( unsigned i=0; i<layers[j]->rang; i++ )
      {                                                      
            // по синапсам
         for( unsigned k=0; k<layers[j]->neuronrang; k++ )   
         {
            layers[j]->neurons[i].deltas[k] = 
                  NiuParm * 
                  (MiuParm*layers[j]->neurons[i].deltas[k] + 
                  (1.-MiuParm)*layers[j]->neurons[i].error *
                  layers[j-1]->neurons[k].axon);

//            if( lastDiff < fabs(layers[j]->neurons[i].deltas[k]) )
//               lastDiff = fabs(layers[j]->neurons[i].deltas[k]);
         }
      }
   }
}

void NetBP::Update( void )
{
    for( unsigned i=0; i<rang; i++ ) 
      layers[i]->Update();
}

void NetBP::Randomize
(
   float range
)
{
    lastDiff = 0.;
    for( unsigned i=0; i<rang; i++ )
       layers[i]->Randomize( range );
}

void NetBP::Cycle
(
   float *Inp, 
   float *Out
)
{
   SetNetInputs( Inp );

//   if( dSigma )
//      AddNoise();

   Propagate();
   CalculateError( Out );
   Learn();
   Update();
}

int NetBP::SaveToFile
(
   const char *file
)
{
   FILE *fp;

   fp = fopen( file, "wt" );
   
   if( fp == NULL ) 
      return 1;
   
   fprintf( fp, "%u", rang );
   
   for( unsigned i=0; i<rang; i++ )
   {
      fprintf(fp,"\n+%u",layers[i]->rang);
      fprintf(fp,"\n|%u",layers[i]->neuronrang);
      
      for( unsigned j=0; j<layers[i]->rang; j++ )
      {
         fprintf(fp,"\n|+%f",layers[i]->neurons[j].state);
         fprintf(fp,"\n||%f",layers[i]->neurons[j].axon);
         fprintf(fp,"\n||%f",layers[i]->neurons[j].error);
         
         for( unsigned k=0; k<layers[i]->neuronrang; k++ )
         {
            fprintf(fp,"\n||%f", layers[i]->neurons[j].synapses[k]);
         }
         fprintf(fp,"\n|+");
      }
      fprintf(fp,"\n+");
   }

   fclose(fp);
   return 0;
}

int NetBP::LoadFromFile
(
   const char *file
)
{
   FILE         *fp;
   unsigned      i, r, nr;
   char          bf[12];
   
//  if( layers )                  // возможно использование только
//      return 1;                  // экземпляров класса, сконструированных по умолчанию
                                 // с помощью NetBP(void).

   if( layers )
   {
      if( rang )
      {
         for( unsigned l=0; l<rang; l++ ) 
            layers[l]->~LayerBP();
         
         delete [] layers; 
         layers = NULL;
      }
   }
   
   fp = fopen(file,"rt");
      
   if( fp == NULL ) 
      return 1;
      
   fscanf(fp,"%u\n",&r);

   try
   {
      if( r == 0 ) 
         throw;
         
      layers = new LayerBP *[r];

      if( layers == NULL )
      { 
         throw;
      }
      else
      {
         rang = r;
         for( i=0; i<rang; i++ ) 
            layers[i] = NULL;
      }
      
      for( i=0; i<rang; i++ )
      {
         fgets( (char *)bf, 10, fp ); 
         
         r  = atoi(bf+1);
         fgets( (char *)bf, 10, fp );
         nr = atoi(bf+1);
         
         layers[i] = new LayerBP( r, nr );
         
         for( unsigned j=0; j<layers[i]->rang; j++ )
         {
            fscanf(fp,"|+%f\n",&(layers[i]->neurons[j].state));
            fscanf(fp,"||%f\n",&(layers[i]->neurons[j].axon));
            fscanf(fp,"||%f\n",&(layers[i]->neurons[j].error));
            
            for( unsigned k=0; k<layers[i]->neuronrang; k++ )
            {
               fscanf(fp,"||%f\n", &(layers[i]->neurons[j].synapses[k]));
            }
            fgets( (char *)bf, 10, fp );
         }
         fgets( (char *)bf, 10, fp );
      }
   }
   catch( int )
   {
      stat = STATE_ERROR;
      fclose(fp); 
      return 2;
   }
   
   fclose(fp);
   return 0;
}

NetBP::NetBP
( 
   unsigned n,                // Количество слоев
   unsigned n1,               // Количество нейронов во входном слое
   ...
)
{
   unsigned i, num, prenum;
   va_list  varlist;

   stat       = STATE_OK;
   rang       = 0; 
   layers     = NULL;
   cycle      = 15; 

   layers = new LayerBP *[n];
    
   if( layers == NULL ) 
   { 
      stat = STATE_ERROR;
   }
   else
   {
      rang = n;
      
      for( i=0; i<rang; i++ ) 
         layers[i] = NULL;
         
      num = n1;
      layers[0] = new LayerBP( num, 0 );
      
      va_start(varlist,n1);
      
      for( i=1; i<rang; i++ )
      {
         prenum    = num;
         num       = va_arg( varlist, unsigned );
         layers[i] = new LayerBP( num, prenum );
      }
      va_end(varlist);
   }
}

/*
int NetBP::LoadNextPattern
(
   float _FAR *IN,
   float _FAR *OU
)
{
   unsigned char buf[256];
   unsigned char *s, *ps;
   int i;
   
   if( pf == NULL ) 
      return 1;
      
   if( imgfile )
   {
      restart:
         for( i=0; i<layers[0]->dy; i++ )
         {
            if(fgets(buf,256,pf)==NULL)
            {
               if( learncycle )
               {
                  rewind(pf);
                  learncycle--;
                  goto restart;
               }
               else return 2;
            }
            
            for( int j=0; j<layers[0]->dx; j++ )
            {
               if( buf[j] == 'x' ) 
                  IN[i*layers[0]->dx+j] = 0.5;
               else 
                  if( buf[j] == '.' ) 
                     IN[i*layers[0]->dx+j] = -0.5;
            }
         }
         
         if( fgets(buf,256,pf) == NULL ) 
            return 3;
            
         for( i=0; i<layers[rang-1]->rang; i++ )
         {
            if( buf[i] != '.' ) 
               OU[i] = 0.5;
            else 
               OU[i] = -0.5;
         }
         return 0;
   }
   
   // "scanf often leads to unexpected results
   // if you diverge from an expected pattern." (!)
   // Borland C On-line Help
   
   start:
   
   if( fgets(buf,250,pf) == NULL )
   {
      if( learncycle )
      {
         rewind(pf);
         learncycle--;
         goto start;
      }
      else return 2;
   }
   
   s=buf;
   for( ;*s==' '; s++ );
   
   for( i=0; i<layers[0]->rang; i++ )
   {
      ps = strchr(s,' ');
      
      if( ps ) 
         *ps=0;
         
      IN[i] = atof(s);
      s=ps+1; 

      for( ; *s==' '; s++ );
   }
   
   if( fgets(buf,250,pf) == NULL ) 
      return 4;
   
   s=buf;
   for( ; *s==' '; s++ );
   
   for( i=0; i<layers[rang-1]->rang; i++ )
   {
      ps = strchr(s,' ');
      if( ps ) 
         *ps = 0;
         
      OU[i] = atof(s);
      
      s = ps + 1; 
      for( ; *s==' '; s++ );
   }
   return 0;
}
*/

int NetBP::IsConverged( void )
{
   for( unsigned i=1; i<rang; i++ )
      if( layers[i]->IsConverged() == 0 ) 
         return 0;
         
   return 1;
}

/*
float NetBP::Change
(
   float In
)
{
   // для бинарного случая
   if( In == 0.5 ) 
      return -0.5;
   else 
      return 0.5;
}

void NetBP::AddNoise( void )
{
   unsigned i, k;
   
   for( i=0; i<dSigma; i++ )
   {
      k = random( layers[0]->rang );
      
      layers[0]->neurons[k].axon = Change( layers[0]->neurons[k].axon );
   }
}
*/
