import java.io.*;

class aGA {
/************************************************************
;                                                           *
;  William M. Spears                                        *
;  Navy Center for Applied Research in AI                   *
;  Naval Research Laboratory                                *
;                                                           *
;  This software is the property of the Department of the   *
;  Navy. Permission is hereby granted to copy all or any    *
;  part of this program for free distribution, however      *
;  this header is required on all copies.                   *
;                                                           *
;************************************************************/

// Original coded in C.  Literal translation into Java by Stephen J. Hartley
// (literally a line-by-line recoding since the C version was dumping core)
// (embarassingly literal)
// April 1996

/*****************************************************************************

. compute and save the x that creates the best f(x) so x can be printed with
  best

. instead of a direct hack from C to Java, rewrite whole program as a
  collection of classes

. use java.util.Random instead of Math.random()

. at least make a crossover interface that n-point and uniform crossover classes
  implement so the crossover method can be polymorphic; constructor for the
  crossover class is passed the n points, <1 or >chromLength-1 both do 1-point

. make termination and myeval into separate classes

. open and write to mylog file

              DONE

. make all arrays dynamically allocated; read in other parameters like CONV, Pc,
  and Pm

              DONE

*****************************************************************************/

static String theArgs[] = null;

   protected static int getarg(int i, int n) {
      int value;
      try value = Integer.parseInt(theArgs[i-1]);
      catch (ArrayIndexOutOfBoundsException e) return n;
      catch (NumberFormatException e) return n;
      return value;
   }

   protected static double getarg(int i, double d) {
      double value;
      try value = Double.valueOf(theArgs[i-1]).doubleValue();
      catch (ArrayIndexOutOfBoundsException e) return d;
      catch (NumberFormatException e) return d;
      return value;
   }

   protected static String getarg(int i, String s) {
      String value;
      try value = theArgs[i-1];
      catch (ArrayIndexOutOfBoundsException e) return s;
      return value;
   }

static int popSize = -1, chromLength = -1, numXoverPts = -1, numExps = -1;

static int PRINT_EVALS =100;      /* print every this much */
static int MAX_EVALS =1000;      /* don't work harder than this */
static double MUTATION=.001;          /* Mutation rate of 0.1% */
static double CROSS_OVER=.6;          /* Crossover rate of 60% */
static double CONV=.89;
static String myLog = "mylog";

// +1 in arrays below so 1..n indexing will work

boolean[][] p1 = new boolean[popSize+1][chromLength+1];    /* Population 1 */
boolean[][] p2 = new boolean[popSize+1][chromLength+1];    /* Population 2 */
int[] sh = new int[popSize+1];        /* Shuffle array */
boolean[] b = new boolean[chromLength+1];        /* Best Individual seen so far */
double[] f = new double[popSize+1];       /* Fitness array */
double[] v1 = new double[popSize+1];  /* Holds prior fitness values - useful
                   for avoiding evaluation of individuals */
double[] v2 = new double[popSize+1];  /* Holds prior fitness values - useful
                   for avoiding evaluation of individuals */

boolean[][] c;      /* Current population pointer */
boolean[][] o;      /* Old population pointer */

double[] c_value;               /* Current prior fitness value pointer */
double[] o_value;               /* Old prior fitness value pointer */

int size;              /* Current size of population */
int length;            /* Current size of individual */
int gen_time;              /* Number of generations */
int bit;               /* The picked bit where mutation did NOT
                      occur in the last generation */
int bits;              /* Total number of bits */
int evals;             /* Number of evaluations done this trial */

int iteration;             /* How many iterations of the GA so far */

boolean new_best;       /* Set to 1 if we have a NEW best individual */

int max_generation;       /* The maximum number of generations to run */

int reevaluations;        /* The number of re-evaluated individuals */

boolean cross_points;      /* Is number of crossover points is > 0 ? */
int num_cross_points;      /* The number of crossover points */

double log_m_rate;         /* log_m_rate = log(1.0 - MUTATION); */
double total_fitness;          /* Total Fitness of everyone */
double total_best_fitness;     /* Total fitness of the best ever eval */
double total_computed_fitness; /* Total fitness of every eval'ed individual */
double best;               /* Best individual seen so far */
double solution;               /* The actual solution to the problem */

double offline_perf;           /* The current offline performance */
double online_perf;            /* The current online performance */
double conv;                   /* The current population convergence */

boolean cross_change;

double global_average_fitness;
double average_fitness;

PrintStream mylog = null;

/* Marker to indicate when a run of the GA is done. */

boolean done;

/* Called with "gac pop_size string_length number_of_cross_over_points
                    number_of_experiments" */

   public static void main (String args[]) {

      theArgs = args;

      popSize     = getarg( 1, popSize);
      chromLength = getarg( 2, chromLength);
      numXoverPts = getarg( 3, numXoverPts);
      numExps     = getarg( 4, numExps);
      myLog       = getarg( 5, myLog);
      PRINT_EVALS = getarg( 6, PRINT_EVALS);
      MAX_EVALS   = getarg( 7, MAX_EVALS);
      MUTATION    = getarg( 8, MUTATION);
      CROSS_OVER  = getarg( 9, CROSS_OVER);
      CONV        = getarg(10, CONV);

      aGA aga = new aGA();
      aga.run(popSize, chromLength, numXoverPts, numExps);
   }

/* Top level function call. Runs the GA n times, opening
   various output files. */

void run (int population_size, int number_of_bits, int points, int n) {

  System.out.println("pop size="+population_size+", number of bits="
    +number_of_bits+", crossover points="+points+", experiments="+n);
  System.out.println("print evals="+PRINT_EVALS+", max evals="+MAX_EVALS+
    ", mutation="+MUTATION+", crossover="+CROSS_OVER+", conv="+CONV);

  solution = Math.pow((double)2.0,(double)number_of_bits)-1.0; /*31 if 5 bits*/
  solution = solution*solution;                    /* 961 if 5 bits */
  System.out.println("solution = " + solution);

  try mylog = new PrintStream(
              new BufferedOutputStream(
              new FileOutputStream(new File(myLog)), 1024));
  catch (IOException e) {
    System.err.println("IOException");
    System.exit(0);
  }

  for (int i = 1; i <= n; i++) {

    /* Perform GA search. */

    ga_search(population_size, number_of_bits, points);

    /* Print the result in the log file. */

    System.out.println("Experiment #" + i);
    System.out.print("Individual ");
    show_best_individual();
    System.out.println(" found in "+evals+" evaluations with score "+ best);
    mylog.flush(); // leave this out and the mylog file is empty!
  }

  return;
}

/* Return t iff the column is converged to one value by CONV percent.*/

boolean column_convergence (int j) {
  int temp = 0;

  for (int i = 1; i <= size; i++) { if (c[i][j]) temp++; }

  if ((temp > (CONV * size)) || (temp < ((1.0 - CONV) * size))) return(true);
  else return(false);
}

/* Return amount of convergence. */

double convergence () {
  int temp = 0;

  for (int j = 1; j <= length; j++) {
    if (column_convergence(j)) temp++;
  }
  return((double)temp / (double)length);
}

/* Report some statistics. */

void report() {
  offline_perf = compute_offline_perf();
  online_perf  = compute_online_perf();
  conv = convergence();

  System.out.println("Online Perf: "+online_perf+", Offline Perf: "+offline_perf+", Best: "+best);
  System.out.println("Convergence: "+conv+", Re-evaluations: "+reevaluations);

  return;
}

/* Return t iff CONV percent of the columns have converged.*/

boolean convergencep () {
  if (convergence() > CONV) return(true);
  else return(false);
}

/*
double myeval (int ind) {
  int counter = 0;

  solution = length;
  for (int x = 1; x<= length; x++) {
    if (c[ind][x]) counter++;
  }

   return((double)counter);
}
*/

double myeval (int ind) {
  double value = 0;

/*
 * fitness = x**2 where x is unsigned binary value of chromosome bits
 */
  for (int j = 1; j<= length; j++) {
    if (c[ind][j]) value += Math.pow((double)2.0, (double)(j-1));
  }
  value = value*value;

   return(value);
}

/* Swap one bit position in two individuals.*/

void cross_swap (int i, int j, int x) {
  boolean tempi, tempj;

  tempi = c[i][x];
  tempj = c[j][x];

  if (tempi != tempj) {
    cross_change = true;
    c[i][x] = tempj;
    c[j][x] = tempi;
  }

  return;
}

/* Either One or Two point cross-over. Mark for re-evaluation if
   a change has occurred.. */

void cross_over (int i, int j, int x1, int y1) {

  for (int x = 1 + x1; x <= y1; x++) cross_swap(i, j, x);

  return;
}

/* Do cross-over of two individuals. The following diagram should help.

        1   2   3                   *B*
    +---------------------------------------------------------------+
  i:    |   1   |   2   |   3   |   |   |   |   |  *B*  |
    +---------------------------------------------------------------+

        1   2   3                   *B*
    +---------------------------------------------------------------+
  j:    |   1   |   2   |   3   |   |   |   |   |  *B*  |
    +---------------------------------------------------------------+

*/

/* Standard n-point crossover, n is the number of crossover points. */

void cross (int n, int i, int j) {
  int x, y;
  int[] cross_array = new int[chromLength+1];

  /* Fill in crossover points into an array. */

  for (int temp = 0; temp < n; temp++) {
    cross_array[temp] = myrand(length);
  }

  /* If odd number of crossover points, make an even number
     by using the length as the last crossover point. */

  if ((n % 2) > 0) {
    n++;
    cross_array[n - 1] = length;
  }

  /* Sort the array. */

  selectsort(n, cross_array);

  /* Perform crossover over the right sections. */

  cross_change = false;

  for (int temp = 0; temp < n; temp = temp + 2) {
    x = cross_array[temp];
    y = cross_array[temp+1];
    if (x != y) cross_over(i, j, x, y);
  }

  if (cross_change) {
    reevaluations = reevaluations + 2;
    c_value[i] = -1.0;      /* re_evaluate */
    c_value[j] = -1.0;      /* re_evaluate */
  }

  return;
}

/* Cross-over a percentage of the population, based on the
   cross-over rate and the population size. The number of
   crossover points (n) should be less than the length of
   the chromosome (string). */

void cross_population () {

  reevaluations = 0;
  for (int i = 1; i <= (CROSS_OVER * size); i = i + 2) {
    cross(num_cross_points, i, i + 1);
  }

  return;
}

/* Standard uniform crossover with .5 probability of swapping
   alleles. Could be generalized to arbitrary probability. */

void uniform_cross (int i, int j) {

  cross_change = false;

  for (int x = 1; x <= length; x++) {
    if (brand()) cross_swap(i, j, x);
  }

  if (cross_change) {
    reevaluations = reevaluations + 2;
    c_value[i] = -1.0;      /* re_evaluate */
    c_value[j] = -1.0;      /* re_evaluate */
  }

  return;
}

/* Apply uniform crossover to a percentage of the population. */

void uniform_cross_population () {

  reevaluations = 0;

  for (int i = 1; i <= (CROSS_OVER * size); i = i + 2) {
    uniform_cross(i, i + 1);
  }

  return;
}

/* This version of the fitness calculation code uses a global
   average of prior fitnesses (produced by the function geval)
   to produce the proper scaling of values. This should work
   better than the current generation minimum. */

double compute_offline_perf () {
  return(total_best_fitness / (double)evals);
}

double compute_online_perf () {
  return(total_computed_fitness / (double)evals);
}

double compute_global_average () {
  return(total_fitness / (double)(size * gen_time));
}

/* Return scaled fitness. */

double compute_value (double value) {
  value = value - (global_average_fitness / 2.0);
  if (value > 0.0) { return(value); }
  else return(0.0);
}

/* Computes scaled fitness for everyone in the population and
   computes various global statistics. Also prints out any new
   best individual. */

void fitness () {
  double value, sum;

  sum = 0.0;
  global_average_fitness = compute_global_average();
  for (int i = 1; i <= size; i++) {
    value = geval(i);
    total_fitness += value;
    value = compute_value(value);
    sum += value;
    f[i] = value;
  }
  average_fitness = sum / (double)size;

  if (new_best) {
    System.out.print("\nThe best individual is ");
    show_best_individual();
    System.out.println(" with score: "+best);
    new_best = false;
  }

  return;
}

/* Convert the fitness values in f[] into partial sums of
   expected number of offspring in f[]. Useful for selection.
   Notice that f[size] is fixed at "size". Mathematically, this
   should happen if the partial sums are allowed to run all
   the way to the end. However, math errors in C can accumulate
   and actually produce rather large discrepancies (which can
   cause the select algorithm to attempt to access an array 1
   step out of bounds). This should fix that problem - although
   some slight bias may be introduced.*/

void offspring () {
  double prior;

  prior = f[1] / average_fitness;
  f[1] = prior;
  for (int i = 2; i < size; i++) {
    f[i] = (f[i] / average_fitness) + prior;
    prior = f[i];
  }
  f[size] = (double)size;

  return;
}

/* Essentially your standard generation GA. The "done" flag
   is set here when you are finished. This is based on the
   termination criterion. The convergence function can be used
   to exit, allowing restarts in the function "ga_search". */

void run_ga (int pop_size, int number_of_bits, int points) {

  cross_points = points > 0;
  num_cross_points = points;

  time_keeper();
  fitness();
  offspring();
  for (int i = 0; !((done = termination()) || convergencep()); i++) {
    /*  for (i = 0; !(done = termination()); i++) {*/

    report();
    ga_select();
    shuffle();
    swap_pops();
    copy_population();
    mutate();

    /* Call to the crossover function */
    if (cross_points) cross_population();
    else uniform_cross_population();

    time_keeper();
    fitness();
    offspring();
  }
  return;
}

/* Initialize GA variables and run GA until done. The "done"
   flag allows the GA to restart as often as needed to find
   the solution. */

void ga_search (int pop_size, int number_of_bits, int points) {
  int last_i = -1;
  done = false;
  init_ga_variables(pop_size, number_of_bits);
  init_ga_population();

  for (int i = 1; !done; i++) {
    iteration = i;
    last_i = i;
    System.out.println("Iteration "+i+" of GA at "+gen_time+" generations");
    run_ga(pop_size, number_of_bits, points);
    if (!done) reinit_everything();
  }

  mylog.print("Individual ");
  fshow_best_individual();
  mylog.print(" with score "+best+" found in "+evals+" evaluations and "+last_i+" iterations.");
  mylog.print("\n");
  return;
}

void store_best_individual (int i) {
  for (int j = 1; j <= length; j++) {
    b[j] = c[i][j];
  }

  return;
}

/* Code for evaluation of an individual in the current population.
   Keep track of the best one seen so far. This version takes advantage
   of previously computed evaluations from the prior generation. If
   a flag is true, there is no need to re-evaluate the individual.
   Mutation and cross-over will set the flag to false, in which case
   re-evaluation must occur. Also, keep track of the number of
   evaluations that occur. This is a better indication of the
   the number of individuals searched (it would be better to maintain
   all individuals, but that would be space intensive!).*/

double geval (int i) {
  double value;

  value = c_value[i];
  if (value != -1.0) { return(value); }
  else {
    value = (double)myeval(i);
    evals++;
    c_value[i] = value;
    if (value > best) {
      new_best = true;
      best = value;
      store_best_individual(i);
    }

    total_best_fitness += best;
    total_computed_fitness += value;

    /* Output statistics. For now this
       code is commented out to skip the statistics.
       If you want statistics, remove the comment
       symbols. Also see run.c. */

      if ((evals % PRINT_EVALS) == 0) {
      mylog.println("evals "+evals);
      mylog.println("online perf "+compute_online_perf());
      mylog.println("offline perf "+compute_offline_perf());
      mylog.println("convergence "+convergence());
      mylog.println("best "+best);
      mylog.println("reevaluations "+reevaluations);
      }
    return(value);
  }
}

/* Initialize GA Variables */

void init_ga_variables (int pop_size, int len) {
  c = p1;
  o = p2;
  c_value = v1;
  o_value = v2;
  size = pop_size;
  length = len;
  gen_time = 0;
  log_m_rate = Math.log(1.0 - MUTATION);
  bit = jump();
  bits = size * length;
  total_fitness = 0.0;
  total_best_fitness = 0.0;
  total_computed_fitness = 0.0;
  best = 0.0;
  evals = 0;

  return;
}

/* Initializes all GA array structures.*/

void reinit_arrays () {
  for (int i = 1; i <= length; i++) {
    b[i] = false;
  }

  for (int i = 1; i <= size; i++) {
    v1[i] = -1;
    v2[i] = -1;
    sh[i] = 0;
    f[i] = 0.0;
    for (int j = 1; j <= length; j++) {
      p1[i][j] = false;
      p2[i][j] = false;
    }
  }

  return;
}

/* Initialize the population with random bits. */

void init_ga_population () {
  System.out.println("Randomly initializing population.");
  for (int i = 1; i <= size; i++) {
    c_value[i] = -1.0;
    for (int j = 1; j <= length; j++) { c[i][j] = brand(); }
  }

  return;
}

/* After one attempt to use the GA algorithm, reinitialize everything
   and try again - this code reinitializes everything. */

void reinit_everything () {
  reinit_arrays();
  c = p1;
  o = p2;
  c_value = v1;
  o_value = v2;
  bit = jump();
  gen_time = 0;
  total_fitness = 0.0;
  total_best_fitness = 0.0;
  total_computed_fitness = 0.0;
  best = 0.0;
  init_ga_population();

  return;
}

/* Swap the two populations that c[] and o[] point to...*/

void swap_pops () {
  boolean[][] temp;
  double[] tempv;

  temp = c;     /* Swap population array pointers */
  c = o;
  o = temp;

  tempv = c_value;  /* Swap value array pointers */
  c_value = o_value;
  o_value = tempv;

  return;
}

/* Return true if the GA should terminate. One can define
   lots of different termination functions, based on the
   number of evaluations, convergence, etc...*/

boolean termination () {
/*
 *return(evals >= MAX_EVALS);
 */

  return(best == solution);
}

/* The actual jump.*/

int jump () {
   return((int)(Math.floor (Math.log(Math.random()) / log_m_rate)));
}

/* Randomly select a bit and flip it. This is different from selecting
   a bit, and then randomly filling that bit location with a 1 or 0!
   The population is considered to be a linear sequence of bits. */

void flip_bit (int bit, int i) {
  c_value[i] = -1.0;
  if (c[i][bit]) {
    c[i][bit] = false;
  }
  else  c[i][bit] = true;

  return;
}

/* Do mutation.
   bit = marks the picked bit NOT mutated in the last generation.
*/

void mutate () {
  int bits, i = 1;

  bits = length;
  while (i <= size) {
    if (bit <= bits) {
      flip_bit(bit, i);
      bit = bit + jump();
    }
    else {
      bit = bit - bits;
      i++;
    }
  }

  return;
}

/* Standard Baker's SUS algorithm */

void ga_select () {
  int i, count;
  double r;

  i = 1;
  count = 1;
  r = Math.random();
 loop: while (true) {
  if (r < f[i]) {
    sh[count] = i;
    r = r + 1.0;
    count++;
    continue loop;
  }
  else if (count <= size) { i++; continue loop; }

  return;
 }
}

/* Copies individual i in the old population to j in the new population.
   Must make sure to copy the value array location now, since that maintains
   useful evaluation information. */

void copy_individual (int i, int j) {
  c_value[j] = o_value[i];
  for (int x = 1; x <= length; x++) c[j][x] = o[i][x];

  return;
}

/* Copy the old population to the new based on the shuffle array.*/

void copy_population () {
  for (int i = 1; i <= size; i++) copy_individual(sh[i], i);

  return;
}

/* Swap elements in the shuffle array */

void swap (int i, int j) {
  int temp;

  temp = sh[i];
  sh[i] = sh[j];
  sh[j] = temp;

  return;
}

/* Shuffle the shuffle array - in order to get a new order
   for the clones in the new population. The shuffle array
   should have been set from the selection function. The
   goal is to have no position dependencies.*/

void shuffle () {

  for (int i = 1; i <= size; i++) swap(i, myrand(size));

  return;
}

/* Keep track of the generation number...*/

void time_keeper () {
  gen_time++;
  System.out.println("\nIteration "+iteration+", Generation "+gen_time+", Evaluations "+evals+":");

  return;
}

/* Return a random integer from low to high */

int rrandom (int low, int high) {
  int nb = high - low + 1;
  return((int)(Math.random() * nb + low));
}

/* Return a random integer from 1 to n inclusive.*/

int myrand (int n) {
  return(rrandom(1, n));
}

/* Return a random bit (0 or 1).*/

boolean brand () {
  return(rrandom(0, 1)==1);
}

/********************************************************/

/* Display individual i. */

void show_individual (int i) {

  for (int j = 1; j <= length; j++) System.out.print(c[i][j]?1:0);
  System.out.print("\n");

  return;
}

/* Display whole population. */

void show_pop () {

  for (int i = 1; i <= size; i++) {
    System.out.print("Individual "+i+": ");
    show_individual(i);
  }

  return;
}

/* Display best individual seen so far. */

void show_best_individual () {

  for (int i = 1; i <= length; i++) System.out.print(b[i]?1:0);

  return;
}

/* Print best individual in a file. */

void fshow_best_individual () {

  for (int i = 1; i <= length; i++) mylog.print(b[i]?1:0);

  return;
}

/********************************************************/

/* Swap takes as input pointers to 2 elements and swaps them. */

void swap_elements(int x, int y, int[] a)   /* Interchange two elements */ {
  int temp;
  temp = a[x];
  a[x] = a[y];
  a[y] = temp;

  return;
}

/* Selectsort takes as input an array and its size and performs a
   selection sort on this array. */

void selectsort(int n, int a[])    /* Perform a selectsort */ {
  int small;
  for(int k = 0; k < n - 1; k++) {
      small = k;
      for(int j = k + 1; j < n; j++) {
      /* Select the smallest */
      if (a[j] < a[small])  /* from among a[k],...,a[n-1]*/
        small = j;      /* and swap with a[k]. */
    }
      swap_elements(k, small, a);
    }

  return;
}

}
