package com.ibm.able.examples.genetic;

//====================================================================
//
// Licensed Materials - Property of IBM
//
// "Restricted Materials of IBM"
//
// Product: com.ibm.able.examples.genetic
//
// (C) Copyright IBM Corp. 1999 All Rights Reserved.
//
// US Government Users Restricted Rights - Use, duplication or
// disclosure restricted by GSA ADP Schedule Contract with
// IBM Corp.
//
//
//                             DISCLAIMER
//                             ----------
//
// This material contains programming source code for your consideration.
// These examples have not been thoroughly tested under all conditions.
// IBM, therefore, cannot guarantee or imply reliability, serviceability,
// performance or function of these programs.  All programs contained
// herein are provided to you "AS IS".  THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY
// DISCLAIMED.
//
//====================================================================

 
import java.util.Enumeration;
import java.util.Hashtable;

import com.ibm.able.Able;
import com.ibm.able.AbleException;
import com.ibm.able.AbleAgent;
import com.ibm.able.AbleUserDefinedFunction;
import com.ibm.able.beans.AbleGeneticObject;

/**
*   This class implements the F6 function described in the first chapter
*    of The Handbook of Genetic Algorithms (Davis)
*  Is used a hybrid (numeric) chromosome representation
 * @version  $Revision: 1.6 $, $Date: 2002/10/16 16:29:41 $
*/
public class TstGeneticFunction2a extends AbleGeneticObject {

	/**
	 * Serialized version identifier in form       YYYYMMDDVerRelModxx
	 */
	protected static final long serialVersionUID = 2001100500100300000L;

	private static final double RANGE = 200.0;
	private static final double RANGE_MIN = -100.0;
	private static final double RANGE_MAX = 100.0;

	public TstGeneticFunction2a() {
		chromosomeLength = 2;
		vocabulary = "real";
		chromosomeType = double[].class;

		crossoverRate = 0.25;
		mutationRate = 0.25;
	}

	public double getFitness() {
		return fitness;
	}

	/**
	* generate a random chromosome for this genetic object
	*
	*/
	public Object getRandomChromosome() {
		double[] chromosome = new double[chromosomeLength];
		chromosome[0] = RANGE_MAX - (Math.random() * RANGE);
		// num between -100 and +100
		chromosome[1] = RANGE_MAX - (Math.random() * RANGE);
		// num between -100 and +100
		return chromosome;
	}

	public static final double factor = 0.00004768372718899898;

	/**
	*  just add up the number of ones in the chromosome
	*/
	public double computeFitness() {

		// this is deterministic, if already computed,
		// no sense if re-doing work
		if (fitnessComputed)
			return fitness;

		double x1, y1;
		x1 = ((double[]) chromosome)[0];
		y1 = ((double[]) chromosome)[1];

		// Note: no need to do scaling here
		//       numbers are already in desired range

		fitness = f6(x1, y1);
		fitnessComputed = true;

		return fitness;
	}

	protected double f6(double x, double y) {

		double ans = 0.0;
		double xSquared = x * x;
		double ySquared = y * y;
		double x2plusy2 = xSquared + ySquared;

		double numerator = Math.pow(Math.sin(Math.sqrt(x2plusy2)), 2) - 0.5;
		double denominator = Math.pow(1.0 + (0.001 * x2plusy2), 2);

		ans = 0.5 - numerator / denominator;

		//  System.out.println("function f6(" + x + "," + y + ")= " + ans) ;
		return ans;
	}

	/**
	 *  Mutate a single chromosome.
	 *
	 *   Note: this operator does a bitwise test against the mutationRate
	 *         and then rolls the dice to select a delta to the current value
	 *
	 *  @param  chromosome The original chromosome array.
	 *
	 *  @return the (potentially) mutated chromosome array.
	 *
	 */
	public double[] mutateChromosome(double[] chromosome) {

		int size = chromosome.length;

		// not sure if I like this, lots of overhead
		// may only want to check mutation rate, then select bit, then value
		// rather than roll the dice on each bit ... kind of expensive computation
		for (int i = 0; i < size; i++) {
			double rand = Math.random();
			int bitPos = 0;
			if (rand < mutationRate) { // see if we should mutate the bit
				if (Able.TraceLog.isLogging()) {
					//searchAgent.getTraceLogger() instead of Able.TraceLog
					Able.TraceLog.text(
						Able.TRC_MEDIUM,
						this,
						"mutateChromosome",
						"mutate bit position = " + i);
				}
				double rand2 = 0.10 * (RANGE_MAX - (Math.random() * RANGE));
				// 10% of 200
				chromosome[bitPos] = rand2 + chromosome[bitPos];
				if (chromosome[bitPos] > RANGE_MAX) {
					chromosome[bitPos] = RANGE_MAX;
				} else if (chromosome[bitPos] < RANGE_MIN) {
					chromosome[bitPos] = RANGE_MIN;
				}
			}
		}
		return chromosome;
	}

	/**
	   *  Given 2 parent chromosomes
	   *   Do a single point crossover (or not), creating 2 children chromosomes
	   *   And mutate bits at the specified rate
	   */
	public Object[] onePointCrossoverAndMutate(Object[] parents) {
		Object[] children = new Object[2];
		double[] c1Chromosome = (double[]) parents[0];
		double[] c2Chromosome = (double[]) parents[1];
		if (Able.TraceLog.isLogging()) {
			//searchAgent.getTraceLogger() instead of Able.TraceLog
			Able.TraceLog.text(
				Able.TRC_MEDIUM,
				this,
				"onePointCrossoverAndMutate",
				"parent1 chromosome = "
					+ c1Chromosome[0]
					+ ", "
					+ c1Chromosome[1]);
			Able.TraceLog.text(
				Able.TRC_MEDIUM,
				this,
				"onePointCrossoverAndMutate",
				"parent2 chromosome = "
					+ c2Chromosome[0]
					+ ", "
					+ c2Chromosome[1]);
		}
		// now, see if we should do a crossover on them
		double rand = Math.random(); // roll the dice
		if (rand < crossoverRate) {
			// ok, do a crossover on the parents
			double p1Left = c1Chromosome[0];
			double p1Right = c1Chromosome[1];
			double p2Left = c2Chromosome[0];
			double p2Right = c2Chromosome[1];

			c1Chromosome[0] = p1Left;
			c1Chromosome[1] = p2Right;
			c2Chromosome[0] = p2Left;
			c2Chromosome[1] = p1Right;
			if (Able.TraceLog.isLogging()) {
				//searchAgent.getTraceLogger() instead of Able.TraceLog
				Able.TraceLog.text(
					Able.TRC_MEDIUM,
					this,
					"onePointCrossoverAndMutate",
					"c1 crossover chromosome = "
						+ c1Chromosome[0]
						+ ", "
						+ c1Chromosome[1]);
				Able.TraceLog.text(
					Able.TRC_MEDIUM,
					this,
					"onePointCrossoverAndMutate",
					"c2 crossover chromosome = "
						+ c2Chromosome[0]
						+ ", "
						+ c2Chromosome[1]);
			}

		}

		// now see if we should mutate any bits
		c1Chromosome = mutateChromosome(c1Chromosome);
		c2Chromosome = mutateChromosome(c2Chromosome);

		children[0] = c1Chromosome;
		children[1] = c2Chromosome;
		return children;
	}

	/**
	*  Given 2 parent chromosomes
	*   Average their values , creating 2 children chromosomes
	*/
	public Object[] average(Object[] parents) {
		Object[] children = new Object[2];
		double[] c1Chromosome = (double[]) parents[0];
		double[] c2Chromosome = (double[]) parents[1];
		if (Able.TraceLog.isLogging()) {
			//searchAgent.getTraceLogger() instead of Able.TraceLog
			Able.TraceLog.text(
				Able.TRC_MEDIUM,
				this,
				"average",
				"parent1 chromosome = "
					+ c1Chromosome[0]
					+ ", "
					+ c1Chromosome[1]);
			Able.TraceLog.text(
				Able.TRC_MEDIUM,
				this,
				"average",
				"parent2 chromosome = "
					+ c2Chromosome[0]
					+ ", "
					+ c2Chromosome[1]);
		}

		// now, see if we should do a crossover on them
		/*double rand =*/ Math.random(); // roll the dice
		// ok, do a crossover on the parents
		double p1Left = c1Chromosome[0];
		double p1Right = c1Chromosome[1];
		double p2Left = c2Chromosome[0];
		double p2Right = c2Chromosome[1];

		c1Chromosome[0] = (p1Left + p2Left) / 2.0;
		c2Chromosome[1] = (p1Right + p2Right) / 2.0;

		if (Able.TraceLog.isLogging()) {
			//searchAgent.getTraceLogger() instead of Able.TraceLog
			Able.TraceLog.text(
				Able.TRC_MEDIUM,
				this,
				"average",
				"c1 crossover chromosome = "
					+ c1Chromosome[0]
					+ ", "
					+ c1Chromosome[1]);
			Able.TraceLog.text(
				Able.TRC_MEDIUM,
				this,
				"average",
				"c2 crossover chromosome = "
					+ c2Chromosome[0]
					+ ", "
					+ c2Chromosome[1]);
		}

		children[0] = c1Chromosome;
		children[1] = c2Chromosome;
		return children;
	}

	/**
	*   Return a hashtable of operator names (keys) and fitness values (Doubles)
	*   fitness values must sum to 100
	*
	*   Note: this hashtable may be extended to using operator fitness objects vs fixed values
	*         so that we can adapt the operators selection during the search process
	*
	*   @return the hashtable with operator fitness values
	*/
	public Hashtable getOperatorFitness() throws AbleException {

		Hashtable operatorFitness = new Hashtable();
		if (searchAgent != null) {
			searchAgent.getUserDefinedFunctions();
			Hashtable udfs = searchAgent.getUserDefinedFunctions();
			Enumeration enum = udfs.keys();
			Double v = new Double(100.0 / udfs.size()); // set all to have equal chance
			while (enum.hasMoreElements()) {
				Object key = enum.nextElement();
				AbleUserDefinedFunction udf =
					(AbleUserDefinedFunction) udfs.get(key);
				// include arity for hash lookup, shows on panel, unfortunately
				// but could redo panel to show name from udf
				//String name = udf.getName();
				operatorFitness.put(udf.getNameWithArity(), v);
			}
		}
		return operatorFitness;
	}

	/**
	  * Register any unique or overridden operators (user-defined functions) with the SearchAgent.
	  *
	  * @param   agent The search agent.
	  */
	public void registerOperators(AbleAgent agent) {
		// this is done in the overridden method, must do here also
		searchAgent = agent; // keep a reference to this agent

		AbleUserDefinedFunction udf;
		try {
			udf =
				new AbleUserDefinedFunction(
					"onePointCrossoverAndMutate",
					this,
					"onePointCrossoverAndMutate",
					new Class[] { Object[].class });
			agent.addUserDefinedFunction(udf);
			udf =
				new AbleUserDefinedFunction(
					"average",
					this,
					"average",
					new Class[] { Object[].class });
			agent.addUserDefinedFunction(udf);
		} catch (Exception e) {
			if (Able.TraceLog.isLogging()) {
				Able.TraceLog.text(
					Able.TRC_LOW,
					this,
					"registerOperators()",
					"AbleGeneticObject Error: can't create UserDefinedFunction for operator "
						+ e.getLocalizedMessage());
				Able.TraceLog.exception(
					Able.TRC_LOW,
					this,
					"registerOperators()",
					e);
			}
			Able.MessageLog.text(
				Able.MSG_ERROR,
				this,
				"registerOperators()",
				e.getLocalizedMessage());
		}

	}

	/**
	*  return a string formatted for display
	*  default is the chromosome string and fitness
	*  if chromosome is not a string, then just fitness is returned
	*  Subclasses should override if necessary
	*
	*  @return a string formatted for display purposes
	*/
	public String toString() {
		return new String(
			((double[]) chromosome)[0]
				+ ","
				+ ((double[]) chromosome)[1]
				+ ": "
				+ String.valueOf(fitness));
	}

	/**
	 * Determine the copyright of this class.
	 *
	 * @return    A String containing this class's copyright statement.
	 *            <p>
	 */
	private static String Copyright() {
		return Able.Copyright;
	}

}
