/**
 * xor_backprop.c
 *
 * This is an example of a backpropagation network to solve the XOR problem.
 * This can also be used as a template for other programs using backpropagation
 * networks.
 *
 * For more information on the backpropagation network, see backprop.h
 *
 * To compile this program with LINUX, first compile the backprop.c file into
 * an object with
 *
 * gcc backprop.c -c -DLINUX
 *
 * Then compile the xor_backprop.c program with
 *
 * gcc xor_backprop.c backprop.o -o xor_backprop
 *
 * written by Joshua Petitt
 * Center for Intelligent Information Processing (CIIPS)
 * University of Western Australia
 * 2003
 */

#include "backprop.h"
#include <stdio.h>
#include <strings.h>

#define IN_SIZE 2			// network input layer size
#define OUT_SIZE 1			// network output layer size
#define HID_SIZE 5			// network hidden layer size

#define MAX_EPOCHS 100000	// maximum number of epochs
#define TOLERANCE 0.05		// maximum allowed error in output vector
#define LEARNING_RATE 0.1	// network learning rate (use small number
					// for more stable training, but longer
					// training time. Use number near 1 for
					// faster training, but network may not converge.
#define TRAINING_SET_SIZE 4

int main(int argc, char* argv[])
{
	int i,j,k;
	float error, tolerance;
	int max_epochs;
	char* filename;

	// Allocate memory for the network.
	float x1[IN_SIZE];
	float W1[HID_SIZE][IN_SIZE];
	float y1[HID_SIZE];
	float g1[HID_SIZE];

	float x2[HID_SIZE];
	float W2[OUT_SIZE][HID_SIZE];
	float y2[OUT_SIZE];
	float g2[OUT_SIZE];


	// Create both layers.  Note that this intializes the layers
	// with references to the memory allocated above.
	layer_t layers[2] =	{
		{IN_SIZE,HID_SIZE,x1,&W1[0][0],y1,g1},
		{HID_SIZE,OUT_SIZE,x2,&W2[0][0],y2,g2}
	};

	// Create the network and initialize with the layers previously
	// allocated.
	network_t network = {2,LEARNING_RATE,layers};


	// The input training set (A XOR B).
	// All enumerations of two binary numbers.
	float X[TRAINING_SET_SIZE][IN_SIZE] = {
					{0,0},
					{0,1},
					{1,0},
					{1,1}
					};

	// The output training set (A XOR B).
	// The desired output of the network.
	float Y[TRAINING_SET_SIZE][OUT_SIZE] = {
					{0},
					{1},
					{1},
					{0}
					};


	// Randomize weights
	RandomizeNetwork(&network);

	// Tnput data to network and activate
	printf("Initial state\n");
	for(j=0;j<TRAINING_SET_SIZE;j++)
	{
		InputToNetwork(&network,&X[j][0]);
		ActivateNetwork(&network);
		PrintNetworkOutput(&network);
	}

	// Train the network.  The training is done in "batches" or "epochs".
	// meaning that each input/output pair is applied once to the network
	// for one epoch.  The training set is then repeatedly applied to the
	// network until the error is below a set minimum threshold.
	printf("\nBatch Training ... ");
	k=0;
	tolerance = TOLERANCE;
	max_epochs = MAX_EPOCHS;
	error = TrainNetwork(&network,&Y[j][0]);
 
    while(error > tolerance && k<max_epochs)
	{
		for(j=0;j<TRAINING_SET_SIZE;j++)
		{
			InputToNetwork(&network,&X[j][0]);
			ActivateNetwork(&network);
			error = TrainNetwork(&network,&Y[j][0]);
		}
		k++;
	}
	
	// Display results of training
	printf("done\nTotal error = %f\n# of epochs %d\n",error,k);
	if(k>=max_epochs)
	{
		printf("warning: maximum epochs reached\n");
	}

	// Input data to network and activate
	printf("\nAfter training\n");
	for(j=0;j<TRAINING_SET_SIZE;j++)
	{
		InputToNetwork(&network,&X[j][0]);
		ActivateNetwork(&network);
		PrintNetworkOutput(&network);
	}

	// Write weight data to a file if filename is present
	if(argc>0)
	{
		filename=NULL;
		for(i=0;i<argc-1;i++)
		{
			if(strcmp("-f",argv[i])==0)
			{
				filename = argv[i+1];
				printf("\nSaving to file %s ",filename);
			}
		}

		if(filename)
		{
			// save the network data
			if(strstr(filename,".h"))
			{
				printf("in HDT format\n");
				SaveNetworkWeightsHDT(&network,filename);
			}
			else
			{
				printf("in binary format\n");
				SaveNetworkWeights(&network,filename);

				// randomize weights
				RandomizeNetwork(&network);

				printf("\nRandomized\n");
				for(j=0;j<TRAINING_SET_SIZE;j++)
				{
					InputToNetwork(&network,&X[j][0]);
					ActivateNetwork(&network);
					PrintNetworkOutput(&network);
				}

				// load the weight data to test file
				LoadNetworkWeights(&network,filename);

				printf("\nLoaded weights\n");
				for(j=0;j<TRAINING_SET_SIZE;j++)
				{
					InputToNetwork(&network,&X[j][0]);
					ActivateNetwork(&network);
					PrintNetworkOutput(&network);
				}
			}

		}
	}

	return 0;
}



