/* =======================================================
	Neural Network Classes for the NeXT Computer
	Written by: Ralph Zazula
					University of Arizona - Fall 1991
					zazula@pri.com (NeXT Mail)
==========================================================*/
#import "Neuron.h"
#import "Random.h"
#import <math.h>

#define N   4				// number of neurons
#define I   3				// input neurons
#define O   1			   // output neurons
#define PREFACT ETA

void main()
{
	id	random = [[Random alloc] init];
	int	i,j,k,n;
	id		neuron;
	id		nList = [[List alloc] init];
	
	int	update_list[N];
	double	inputs[I];
	double	target[O];
	
	long		count;
	int		K = 10;
	double	T = 10.0, Tf = 0.01, ETA = 0.1, Gamma = 0.995, E = 0.0;
	
	//
	// create the neurons
	//
	for(i=0; i<N; i++) {
		[nList addObject:[[[[[[Neuron alloc] init] 
								setRandom:random] 
								setType:Tanh]
								setTemp:T]
								setSymmetric:YES]];
	}
	//
	// make the connections (fully-connected)
	//
	for(i=0; i<N; i++)
		for(j=0; j<N; j++)
			if(j!=i)
				[[nList objectAt:i] 
					connect:[nList objectAt:j]
					withWeight:(1.0 - 2.0*[random percent])];	
	while( T > Tf ) {
	 for(k=0;k<K;k++) {
		//
		// apply inputs
		// 
		for(i=0; i<I; i++) {
			inputs[i] = [random percent] > 0.5 ? -1 : 1;
		}
		inputs[2] = 1.0;
		target[0] = (inputs[0] == inputs[1] ? 1.0 : -1.0);
		
		for(i=0; i<I; i++)
			[[nList objectAt:i] setOutput:inputs[i]];
		//
		// clamp outputs
		//
		for(i=I; i<I+O; i++)
			[[nList objectAt:i] setOutput:target[i-I]];
		
		// 
		// do "clamped" training
		//	
		for(i=0; i<N; i++) {					// loop over hidden nodes
			update_list[i] = n = [random randMin:I max:(N-1)];
			neuron = [nList objectAt:n];
			if(n > I+O)								// don't unclamp outputs
				[neuron step];	
			for(j=0; j<N; j++) {
				if(j!=n) {
					[neuron changeWeightFor:[nList objectAt:j] 
						by:(PREFACT)*[neuron lastOutput]*
							[[nList objectAt:j] lastOutput]];
			 	}
			}
		}
		//
		// do "free" (un)learning
		//
		for(i=I; i<N; i++)
			[[nList objectAt:i] step];					// unclamp outputs
		for(i=0; i<N; i++) {							// loop over all nodes
			n = update_list[i];
			neuron = [nList objectAt:n];
			[neuron step];
			for(j=0; j<N; j++) {
			   if(j!=n) {
					[neuron changeWeightFor:[nList objectAt:j] 
						by:-(PREFACT)*[neuron lastOutput]*
							[[nList objectAt:j] lastOutput]];
				}
			}
		}
		if((count %1) == 0) {
			for(i=0; i<O; i++)
			 E += (0.5/O)*fabs(target[i] - [[nList objectAt:I+i] lastOutput]);
			if(E == 0.0) E = MINFLOAT;
		}
	}
		printf("%e %e\n",T,E);
		E=0.0;
		
		T = Gamma*T;
		for(i=0; i<N; i++)
			[[nList objectAt:i] setTemp:T];
	}
	 
	//
	// dump the weights
	//
	for(i=0; i<N; i++) {
		neuron = [nList objectAt:i];
		for(j=0; j<N; j++)
			if(j!=i)
				printf("%e ",[neuron getWeightFor:[nList objectAt:j]]);
		printf("\n");
	}
	printf("ETA: %e K: %u Gamma: %e\n",ETA,K,Gamma);
	printf("N: %u I: %u O: %u\n",N,I,O);
}
