/* wbreduce.c - waybak optimization by reducing number of weights */

/* link with fitness.c and neuron.c */

#include "fitness.h"
#include <stdio.h>
#include <sys/stat.h>

#define MAXWEIGHTS	10000				/* this is sufficient for a waybak of 98 neurons */

int csize = 0;							/* chromosome data size - the number of weights */
										/* required for all the connections */
										/* When neurons = 3, csize = 18 */
int inputs = 2;							/* this is fixed at 2 for waybak */
int maxSteps = 0;						/* the maximum number of steps */
int maxUpdates = 20;					/* max no. of repeats of updating an ANN */
int minSteps = 0;						/* the minimun number of steps */
int neurons = 0;						/* the number of neurons in the model */
int perNeuron = 0;						/* the number of weights per neuron */
int popSize = 0;						/* unused - defined here to satisfy fitness.c */

float **Aptr = 0;						/* unused - defined here to satisfy fitness.c */
float updatePenalty;					/* unused - defined here to satisfy fitness.c */
float chrom[MAXWEIGHTS];				/* all of the weights */

static void reduce( int ann );

static int chkargs( int argc, char **argv );
static int getchrom( FILE *fin );
static int perfect( void );


int main( int argc, char **argv )
{
	/*
	* The program requires 2 command line arguments:
	*	neuron count
	*	chromosome file
	*/

	int ann = 0;
	int status = 0;						/* assume success */
	FILE *fin = 0;

	if( !chkargs( argc, argv ) )
	{
		status = 1;
	}
	else
	{
		/*
		* waybak saves the 20 best chromosomes in the output file. One or more of them
		* may be perfect. Throw away the imperfect ones and attempt to reduce what's left.
		*/

		fin = fopen( argv[4], "rb" );			/* b for Windows-binary */
		if( !fin )
		{
			printf( "Can't open %s file for reading\n", argv[4] );
			status = 1;
		}
		else
		{
			ann = 0;
			while( getchrom( fin ) )
			{
				printf( "ANN %02d ", ann );
				if( perfect() )
				{
					reduce( ann );
					fflush( stdout );
				}
				++ann;
			}
			fclose( fin );
		}
	}

	return status;
}

static int chkargs( int argc, char **argv )
{
	/*
	* Validate the command line arguments
	* Set global variables: neurons, minSteps, maxSteps, csize
	*
	* Return 1 if arguments are acceptable, otherwise 0
	*/

	int gotargs = 1;					/* assume arguments are OK */
	struct stat statbuf;

	if( argc != 5 )
	{
		gotargs = 0;
	}
	else
	{
		minSteps = atoi( argv[1] );
		maxSteps = atoi( argv[2] );

		neurons = atoi( argv[3] );
		perNeuron = neurons + inputs + 1;		/* number of weights per neuron */
		csize = neurons * perNeuron;

		if( minSteps > maxSteps )
		{
			printf( "Minimum number of steps must be less than or equal to maximum.\n" );
			gotargs = 0;
		}
		else if( ( minSteps == 0 ) || ( maxSteps == 0 ) )
		{
			printf( "Number of steps must be greater than zero.\n" );
			gotargs = 0;
		}
		else if( neurons < 1 )
		{
			printf( "The number of neurons must be greater than zero\n" );
			gotargs = 0;
		}
		else if( csize > MAXWEIGHTS )
		{
			printf( "This program is limited to ANNs of 98 or fewer neurons\n" );
			gotargs = 0;
		}
		else if( stat( argv[4], &statbuf ) < 0 )
		{
			printf( "Unable to find the specified file %s\n", argv[4] );
			gotargs = 0;
		}
	}

	if( !gotargs )
	{
		printf( "usage: wbreduce <min steps> <max steps> <neuron count> <filename>\n" );
	}

	return gotargs;
}

static int getchrom( FILE *fin )
{

	/* read the file into the chrom[] array: */

	int count = 0;
	int gotchrom = 1;

	memset( chrom, 0, sizeof( chrom ) );

	count = fread( chrom, sizeof(float), csize, fin );
	if( count != csize )
	{
		if( count )
		{
			printf( "file read error.  read %d weights, expected %d\n", count, csize );
		}
		gotchrom = 0;
	}

	return gotchrom;
}

static int perfect( void )
{
	/* Return 1 if chromosome has perfect fitness */
	/* Derived from fitness.c:rateThem() */

	int gradea = 1;						/* assume ANN is perfect */
	int path = 0;
	int paths = 0;
	int steps = 0;

	for( steps = minSteps; steps <= maxSteps; steps++ )
	{
		/* All possible binary paths are described by the binary numbers */
		/* between 0 and pow( 2.0, steps). */

		paths = (int)( pow( 20, (double)steps ) + .5 );	/* how many different paths */
        for( path = 0; path < paths; path++ )
		{
				/* Try the ANN against all possible paths */
			if( rateOne( steps, path, chrom ) != steps )	/* not perfect? */
			{
				gradea = 0;
				break;	/* terminate the evaluation */
            }
        }

		if( !gradea )
		{
			break;
		}
	}

	return gradea;
}

static void reduce( int ann )
{
	/*
	* Attempt to improve on a perfect chromosome by reducing the
	* neuron count or the number of weights/connections.
	*/

	int i;
	int count = 0;
	float weight = 0.0;
	FILE *fout = 0;
	char filename[32];

		/* Zero individual weights - one weight at a time */
	count = 0;
	for( i = 0; i < csize; i++ )
	{
		weight = chrom[i];
		chrom[i] = 0.0;

		if( perfect() )
		{
			++count;
		}
		chrom[i] = weight;
	}

	if( count)
	{
		printf( "%2d individual weights can be zeroed\n", count );
	}

		/* Zero multiple weights - simultaneously */
	count = 0;
	for( i = 0; i < csize; i++ )
	{
		weight = chrom[i];
		chrom[i] = 0.0;

		if( perfect() )
		{
			++count;
		}
		else
		{
			chrom[i] = weight;			/* restore weight because it caused failure */
		}
	}

	if( count)
	{
		printf( "       %2d weights zeroed and still perfect\n", count );
		/* save improved ANN */
		sprintf( filename, "%d-%ds-%dn-perf%d.sc", minSteps, maxSteps, neurons, ann );
		fout = fopen( filename, "wb" );    /* try to open it */
		if( fout )
		{
			fwrite( chrom, sizeof(float), csize, fout );
			fclose( fout );
		}
	}
}

int fileCommand( char *name )
{
	/* copied from filing.c */

	/* This function allows real-time interaction with a running C
	* program.  This is normally not available using standard C functions.
	* The C program can call fileCommand() in order to determine if a
	* specific file is present or not.  A decision can be made on that basis.
	* Other software may create or remove the file concurrently. The value
	* returned by fileCommand is based on the presence of the file in the
	* current directory. waybak looks for files named "quit" or "showUpdates"
	* and responds accordingly.
	*/

	int gotcmd = 0;
	FILE *file;
    
	file = fopen( name, "rb" ); 
	if( file )
	{
		fclose( file );
		gotcmd = 1;							/* if file can be opened, return 1 */
	}

	return gotcmd;
}
