

import java.io.*;
import java.util.*;

public class PredictionGAwithPittLCS
{
  public int popsize;
  private int series_length;
  private int chrom_length;
  private int rule_length;
  private int num_rules_per_chrom;

  public double[] fitnesses;

  private char [] binarySeries;

  public PredictionGAwithPittLCS(int pop_size_input, int series_length_input, int chrom_length_input, int rule_length_input)
  {
	  popsize=pop_size_input;
	  fitnesses = new double[popsize];
	  series_length = series_length_input;
	  binarySeries = new char[series_length];
	  chrom_length = chrom_length_input;
	  rule_length= rule_length_input;
	  num_rules_per_chrom = chrom_length/rule_length;
  }

  private void readFile() throws Exception
  {
     String s;
     int i,j,line_length;
     j= 0;
	  BufferedReader input = new BufferedReader(new FileReader("BTSPdata.txt"));
	  s = input.readLine();
	  while(s != null)
		 {
		   line_length = s.length();
		   for(i = 0; i < line_length; i++)
			   binarySeries[j++] = s.charAt(i);
		   s = input.readLine();
		  }
	   input.close();
   }


  public void objective(char chromosomes[][])
  {

	  char[] chromosome = new char[chrom_length];   //  new for CS 326:  I made a char array
	                                                //                   to store a single
	                                                //      chromosome, which needs to be evaluated.

	  for(int j = 0; j < popsize; j++)   //  Go through pop
	    {

         for(int i = 0; i < chrom_length; i++)   //  CS 326   This loop gets the j_th chromosome from
             chromosome[i] = chromosomes[i][j];  //  CS 326   the population of chromosomes, and stores
                                                 //           them in the char array "chromosome" for
                                                 //      you to hash on. (chromosome is the "key" for table.)
 /*       if(Hashtable.contains(chromosome))    //  CS 326:  THIS IS PSEUDOCODE!  Gives basic idea of how
             fitnesses[j] = (double) Hashtable.get(chromosome);   //  to check hashtable first, to see if
                                                                  //  the j_th chromosome is in there.  If
                                                                  //  hashtable contains j_th chromosome, then
                                                                  //  just get the fitness (which is the "value"
                                                                  //  for the table) and store it in the j_th
                                                                  //  spot in the fitness array.

             else                                           // Otherwise, if j_th chromosome NOT in hashtable,
               {                                            // then must evaluate it using the fitness function
                                                            // (code below) AND, then, don't forget to "put"
                                                            // the newly computed fitness into the hashtable,
                                                            // paired with the chromosome.   (See comments 30
                                                            // lines below.
 */

					 fitnesses[j] = 0.0;
					 for(int k = 0; k < series_length-rule_length; k++)  //  Go through time series (bit array)
						{
							int num_votes_for_a_one = 0;       //  Determine the rule group's decision
							for(int r = 0; r < num_rules_per_chrom; r++)   //  by checking what each rule in the group "votes" (0, or 1)
							  {
								  int i = r*rule_length;     // For each rule in the group
								  while(i < (r+1)*rule_length      //  Compare the rule to the current set of bits from the series...
										&& (chromosomes[i][j]=='#'     //  See if the rule matches...   '#'s match anything
										|| chromosomes[i][j] ==  binarySeries[k+(i-r*rule_length)]))
										i++;
								  if(i == (r+1)*rule_length)  //  If this is true, then rule r matched the input bits
									 num_votes_for_a_one++;   //   which means rule r votes for a "1" as next bit
							  }

							//  Implement Nate's "Squeaky Wheel Rule", which means if any one rule in group (chrom) votes for a "one"
							//    then they all go along with that (the group/chrom predicts a one for the next bit).  Otherwise (if
							//    no rule votes for a one), then the group votes for a zero as the next bit.

							if((num_votes_for_a_one >0 && binarySeries[k+rule_length] == '1')  // If any rule in chrom. voted for a one,
																							   //  then the whole chrom. votes for a one, and then if
										 ||                                                    //  next bit really IS a one, then incr. chrom's fitness.
																							   //  OR, if no rule in chrom. voted for a one, then the whole
									   (num_votes_for_a_one == 0  && binarySeries[k+rule_length] == '0'))  //  chrom votes for a zero, and then if the next
																							   //  bit really IS a zero, then incr. chrom.'s fitness.
									 fitnesses[j] += 1.0;
						 }
/*             and put (chromosome, fitnesses[j]) into Hashtable.   //  CS 326  PSEUDOCODE!  Put the new
                                                                    //  fitness into hashtable, paired with
                                                      //  the chromosome.
*/
        }
   }


  public static void main(String args[]) throws Exception
  {
	final int CHROM_LENGTH = 64;   //  CHROM_LENGTH = RULE_LENGTH  * number of rules in chrom
	final int RULE_LENGTH =8;       //   so make sure CHROM_LENGTH is a multiple of RULE_LENGTH!
	final int SERIES_LENGTH = 10000;   //  this is how many bits are used in obj. fcn.
	final int POPSIZE= 1000;           //  Population size

	final long RANDOM_SEED = 1567890;
	Random randomizer= new Random(RANDOM_SEED);

    final int MAX_NUM_GENS = 5000;    //   SIMPLE GA PARAMETERS
    final double PROB_CROSSOVER= 0.9;   //  for each two parents, prob. children will be xover of parents
    final double PROB_MUTATE = 0.00;    //  bit-wise prob. of bit-flip
    final int tourn_size = 2;
    final double DONT_CARE_PROBABILITY = 0.5;   // Prob. of a '#' per bit position in
                                            // each chromosome of each individual in
                                            // initial population (gen 0).



	double worst, best, avg;   //  Run stats
	int index_of_best;


	int gen_num = 0;
	int competitor_1;
	int competitor_2;
	int winner;

	int parent_1, parent_2;
	int crossover_pt_1, crossover_pt_2;
	int temp;

   //  Open files for writing fitness stats each generation
    PrintStream best_of_gen_file = new PrintStream(new FileOutputStream("best_of_gen.txt"));
    PrintStream avg_of_gen_file = new PrintStream(new FileOutputStream("avg_of_gen.txt"));
    PrintStream worst_of_gen_file = new PrintStream(new FileOutputStream("worst_of_gen.txt"));

    //  Create BB simulator.  Create chrom array.
    PredictionGAwithPittLCS predictor = new PredictionGAwithPittLCS(POPSIZE, SERIES_LENGTH, CHROM_LENGTH, RULE_LENGTH);
    predictor.readFile();
    char pop[][]= new char[CHROM_LENGTH][POPSIZE];    //  Current pop of bit string chroms


    //  Generate initial pop. randomly
    for(int j=0; j< POPSIZE;j++)
      for (int i = 0;i < CHROM_LENGTH;i++)
           if(randomizer.nextDouble()< DONT_CARE_PROBABILITY)
              pop[i][j] = '#';
              else if(randomizer.nextBoolean())
                 pop[i][j] = '0';
                 else pop[i][j] = '1';


    //  Generation loop
    while(gen_num < MAX_NUM_GENS)
       {


		 // evaluate population
           predictor.objective(pop);


         //  Print population to console

/*		   System.out.print("pop = ");
		   for(int j=0;j<POPSIZE;j++)
			 {
				 for (int i = 0;i < CHROM_LENGTH;i++) System.out.print("" + ((pop[i][j])?("1"):("0")));
		         System.out.println();
			 }
*/		   System.out.println();

         //  Compute stats on pop. and print

           worst = predictor.fitnesses[0];
           index_of_best = 0;
           best = worst;
           avg = 0.0;

		   for(int j=0;j<POPSIZE;j++)
			  {
//				  System.out.println("" + nycBB.fitnesses[j]);

			      if(predictor.fitnesses[j] > best)
			      {
					  best = predictor.fitnesses[j];
					  index_of_best = j;
				  }
			      if(predictor.fitnesses[j] < worst) worst = predictor.fitnesses[j];
			      avg += predictor.fitnesses[j];
			  }
		   avg = avg/POPSIZE;
		   System.out.println("------------" + gen_num +"---------------------");
		   System.out.print("Best design in pop:     " + best + "     ");
		   for(int h = 0; h < CHROM_LENGTH; h++)
		       if(h%RULE_LENGTH == 0)
		           System.out.print(" "+pop[h][index_of_best]);
				   else System.out.print(""+pop[h][index_of_best]);
		   System.out.println();
		   System.out.println("Avg. cost of designs:   " + avg);
		   System.out.println("Worst design in pop:    " + worst);
		   System.out.println();

           //   Print fitness stats to fitness stats files

		   best_of_gen_file.println(best);
		   avg_of_gen_file.println(avg);
		   worst_of_gen_file.println(worst);

		   char new_pop[][] = new char[CHROM_LENGTH][POPSIZE];

          //  Selection
           for(int j = 0; j < POPSIZE; j++)
              {
				  competitor_1 = randomizer.nextInt(POPSIZE);
				  competitor_2 = randomizer.nextInt(POPSIZE);

				  if(predictor.fitnesses[competitor_1] > predictor.fitnesses[competitor_2])
				     winner = competitor_1;
				     else winner = competitor_2;

				  for(int i = 0; i < CHROM_LENGTH; i++)
				     new_pop[i][j] = pop[i][winner];
			  }

           pop = new_pop;



		   // CROSSOVER  (two point)

           if(PROB_CROSSOVER > 0.0)  //  if crossover turned off, skip it!
           {
		     new_pop = new char[CHROM_LENGTH][POPSIZE];

             for(int j = 0; j < POPSIZE; j+=2)
              {
				  parent_1 = randomizer.nextInt(POPSIZE);
				  parent_2 = randomizer.nextInt(POPSIZE);

                  if(randomizer.nextDouble() > PROB_CROSSOVER)
                    {
				      crossover_pt_1 = CHROM_LENGTH;
					  crossover_pt_2 = CHROM_LENGTH;
				    }
				    else
				      {
					    crossover_pt_1 = randomizer.nextInt(CHROM_LENGTH);
				        crossover_pt_2 = randomizer.nextInt(CHROM_LENGTH);
				        if(crossover_pt_2 < crossover_pt_1)
						  {
							 temp = crossover_pt_1;
							 crossover_pt_1 = crossover_pt_2;
							 crossover_pt_2 = temp;
					       }
					  }



				  for(int i=0; i < crossover_pt_1; i++)
				      {
						  new_pop[i][j]   = pop[i][parent_1];
						  new_pop[i][j+1] = pop[i][parent_2];
					  }
				  for(int i=crossover_pt_1; i < crossover_pt_2; i++)
				      {
						  new_pop[i][j]   = pop[i][parent_2];
						  new_pop[i][j+1] = pop[i][parent_1];
					  }
				  for(int i=crossover_pt_2; i < CHROM_LENGTH; i++)
				      {
						  new_pop[i][j]   = pop[i][parent_1];
						  new_pop[i][j+1] = pop[i][parent_2];
					  }
			    }
              pop = new_pop;
		    }  //   END CROSSOVER


		   // MUTATION
          if(PROB_MUTATE > 0.0)  // if mutation turned off, skip this part
           {
		     new_pop = new char[CHROM_LENGTH][POPSIZE];

             for(int j = 0; j < POPSIZE; j++)   // For each indiv.
				  for(int i=0; i<CHROM_LENGTH; i++)   // For each bit...
				      if(randomizer.nextDouble() > PROB_MUTATE)  // bit-wise prob. of mutation
				        new_pop[i][j] = pop[i][j];
				        else if (pop[i][j] == '#')
				                if(randomizer.nextBoolean())
				                   new_pop[i][j] = '0';
				                   else new_pop[i][j] = '1';
				                else if(randomizer.nextBoolean())
				                   new_pop[i][j] = '#';
				                   else if(pop[i][j] == '0')
				                        new_pop[i][j]='1';
				                        else new_pop[i][j]= '0';
            pop = new_pop;
		   }   //  END MUTATION

        gen_num++;

	   }   //  END OF MAIN GENERATION while LOOP


   //  Close fitness stats files

		  best_of_gen_file.println();
       	  avg_of_gen_file.flush();
      	  worst_of_gen_file.close();

    System.exit(0);    // end of program, end of run
  }
}















