/* =======================================================
	Neural Network Classes for the NeXT Computer
	Written by: Ralph Zazula
					University of Arizona
					zazula@bonehead.tucson.az.us (NeXT Mail)
==========================================================*/
#import "BackPropEngine.h"

   double *error;
   
@implementation BackPropEngine

- inputs { return inputs; }
- hidden { return hidden; }
- outputs { return outputs; }
- (double)getEta { return ETA; }
- setEta:(double)newEta { ETA = newEta; return self;}
- (double)getError { return Error; }

- init
{
   [super init];
   
   inputs = [[List alloc] init];
   hidden = [[List alloc] init];
   outputs = [[List alloc] init];
   random = [[Random alloc] init];
   
   ETA = 0.9;
   ALPHA = 0.9;
   Error = 0.0;
   
   return self;
}

- initWithInputs:(int)Nin hidden:(int)Nhid outputs:(int)Nout
{
   int i,j;
   
   [self init];
   
   //
   // create the nodes
   //
   for(i=0; i<Nin; i++)
      [inputs addObject:[[[Neuron alloc] init] 
						setRandom:random]];
   for(i=0; i<Nhid; i++)
      [hidden addObject:[[[Neuron alloc] init] 
						setRandom:random]];   
   for(i=0; i<Nout; i++)
      [outputs addObject:[[[Neuron alloc] init] 
						setRandom:random]];
      
   //
   // make the connections
   //
   for(j=0; j<Nhid; j++)
      for(i=0; i<Nin; i++) 
         [[hidden objectAt:j] connect:[inputs objectAt:i]];
   for(j=0; j<Nout; j++)
      for(i=0; i<Nhid; i++)   
         [[outputs objectAt:j] connect:[hidden objectAt:i]];
   //
   // allocate other variables
   //
   error = (double *)malloc((Nout+1)*sizeof(double));
   
   return self;
}

- applyInput:(double *)input
{
   int   i;
   
   for(i=0; i<[inputs count]; i++) 
      [[inputs objectAt:i] setOutput:(double)input[i]];
   
   for(i=0; i<[hidden count]; i++)
      [[hidden objectAt:i] step];
      
   for(i=0; i<[outputs count]; i++)
      [[outputs objectAt:i] step];

   return self;
}

- correctWithTarget:(double *)target
{
   int i,j,k;
   id oNode, hNode, iNode;
   double O, delta;
   
   //
   // correct the hidden->output weights
   // and update the error
   //
   Error=0.0;
   for(i=0; i<[outputs count]; i++) {
      oNode = [outputs objectAt:i];
      O = [oNode lastOutput];
      error[i] = target[i] - O;
      Error += error[i]*error[i]/2.0;
      for(j=0; j<[hidden count]; j++) {
         hNode = [hidden objectAt:j];
         [oNode changeWeightFor:hNode 
                by:error[i]*[hNode lastOutput]*ETA*O*(1-O)];
      }
   }
   //
   // correct the input->hidden weights
   //
   for(k=0; k<[inputs count]; k++) {
      iNode = [inputs objectAt:k];
      for(j=0; j<[hidden count]; j++) {
         hNode = [hidden objectAt:j];
         delta = 0.0;
         for(i=0; i<[outputs count]; i++) {
            oNode = [outputs objectAt:i];
            O = [oNode lastOutput];
            delta +=O*(1-O)*error[i]*[oNode getWeightFor:hNode];
         }
         [hNode changeWeightFor:iNode 
                by:delta*ETA*[iNode lastOutput]*
                [hNode lastOutput]*(1-[hNode lastOutput])];
      }
   }
   
   return self;
}

@end
