/* * This file is part of JGAP. * * JGAP offers a dual license model containing the LGPL as well as the MPL. * * For licensing information please see the file license.txt included with JGAP * or have a look at the top of class org.jgap.Chromosome which representatively * includes the JGAP license policy applicable for any file delivered with JGAP. */ package org.jgap.gp.impl; import java.io.*; import java.util.*; import org.apache.log4j.*; import org.jgap.*; import org.jgap.event.*; import org.jgap.gp.*; import org.jgap.gp.terminal.*; import org.jgap.util.*; /** * Genotype for GP Programs. * * @author Klaus Meffert * @since 3.0 */ public class GPGenotype implements Runnable, Serializable, Comparable { /** String containing the CVS revision. Read out via reflection!*/ private final static String CVS_REVISION = "$Revision: 1.56 $"; private transient static Logger LOGGER = Logger.getLogger(GPGenotype.class); /** * The array of GPProgram's that makeup the GPGenotype's population */ private GPPopulation m_population; /** * The current configuration instance */ private GPConfiguration m_configuration; private transient static GPConfiguration m_staticConfiguration; /** * Fitness value of the best solution */ private double m_bestFitness; /** * Sum of fitness values over all chromosomes */ private double m_totalFitness; /** * Best solution found */ private IGPProgram m_allTimeBest; private double m_allTimeBestFitness; /** * Is full mode with program construction allowed? */ private boolean m_fullModeAllowed[]; /** * Return type per chromosome */ private Class[] m_types; /** * Argument types for ADF's */ private Class[][] m_argTypes; /** * Available GP-functions. */ private CommandGene[][] m_nodeSets; /** * Minimum depth per each chromosome */ private int[] m_minDepths; /** * Maximum depth per each chromosome */ private int[] m_maxDepths; /** * Maximum number of nodes allowed per chromosome (when exceeded program * aborts) */ private int m_maxNodes; /** * True: Output status information to console */ private boolean m_verbose; private Map m_variables; private IGPProgram m_fittestToAdd; private boolean m_cloneWarningGPProgramShown; // private boolean[] disabledChromosomes; /** * Default constructor. Ony use with dynamic instantiation. * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public GPGenotype() throws InvalidConfigurationException { init(); } /** * Preferred constructor to use, if not using the static method * randomInitialGenotype. * * @param a_configuration the configuration to use * @param a_population the initialized population to use * @param a_types the type for each chromosome, the length of the array * represents the number of chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each chromosome, * must be an array of arrays, the first dimension of which is the number of * chromosomes and the second dimension of which is the number of nodes * @param a_minDepths contains the minimum depth allowed for each chromosome * @param a_maxDepths contains the maximum depth allowed for each chromosome * @param a_maxNodes reserve space for a_maxNodes number of nodes * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public GPGenotype(GPConfiguration a_configuration, GPPopulation a_population, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes) throws InvalidConfigurationException { // Sanity checks: Make sure neither the Configuration, the array // of Chromosomes, nor any of the Genes inside the array are null. // --------------------------------------------------------------- if (a_configuration == null) { throw new IllegalArgumentException( "The configuration instance must not be null."); } if (a_population == null) { throw new IllegalArgumentException( "The population must not be null."); } for (int i = 0; i < a_population.size(); i++) { if (a_population.getGPProgram(i) == null) { throw new IllegalArgumentException( "The GPProgram instance at index " + i + " in population" + " is null, which is forbidden in general."); } } init(); m_types = a_types; m_argTypes = a_argTypes; m_nodeSets = a_nodeSets; m_maxDepths = a_maxDepths; m_minDepths = a_minDepths; m_maxNodes = a_maxNodes; setGPPopulation(a_population); setGPConfiguration(a_configuration); m_variables = new Hashtable(); m_allTimeBestFitness = FitnessFunction.NO_FITNESS_VALUE; // Lock the settings of the configuration object so that it cannot // be altered. // --------------------------------------------------------------- getGPConfiguration().lockSettings(); } protected void init() { // disabledChromosomes = new boolean[100]; } /** * Creates a genotype with initial population for the world set. * * @param a_conf the configuration to use * @param a_types the type of each chromosome, the length is the number of * chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each * chromosome, must be an array of arrays, the first dimension of which is the * number of chromosomes and the second dimension of which is the number of * nodes. Note that it is not necessary to include the arguments of a * chromosome as terminals in the chromosome's node set. This is done * automatically * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_verboseOutput true: output status information to console * * @return created genotype with initialized population * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public static GPGenotype randomInitialGenotype(final GPConfiguration a_conf, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int a_maxNodes, boolean a_verboseOutput) throws InvalidConfigurationException { int[] minDepths = null; int[] maxDepths = null; return randomInitialGenotype(a_conf, a_types, a_argTypes, a_nodeSets, minDepths, maxDepths, a_maxNodes, a_verboseOutput); } /** * Creates a genotype with initial population for the world set. * * @param a_conf the configuration to use * @param a_types the type of each chromosome, the length is the number of * chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each * chromosome, must be an array of arrays, the first dimension of which is the * number of chromosomes and the second dimension of which is the number of * nodes. Note that it is not necessary to include the arguments of a * chromosome as terminals in the chromosome's node set. This is done * automatically * @param a_minDepths array of minimum depths to use: for each chromosome * one entry * @param a_maxDepths array of maximum depths to use: for each chromosome * one entry * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_verboseOutput true: output status information to console * * @return created genotype with initialized population * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public static GPGenotype randomInitialGenotype(final GPConfiguration a_conf, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, boolean a_verboseOutput) throws InvalidConfigurationException { boolean[] fullModeAllowed = new boolean[a_types.length]; for (int i = 0; i < a_types.length; i++) { fullModeAllowed[i] = true; } return randomInitialGenotype(a_conf, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes, fullModeAllowed, a_verboseOutput); } /** * Creates a genotype with a randomly created initial population. * * @param a_conf the configuration to use * @param a_types the type of each chromosome, the length is the number of * chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each * chromosome, must be an array of arrays, the first dimension of which is the * number of chromosomes and the second dimension of which is the number of * nodes. Note that it is not necessary to include the arguments of a * chromosome as terminals in the chromosome's node set. This is done * automatically * @param a_minDepths array of minimum depths to use: for each chromosome * one entry * @param a_maxDepths array of maximum depths to use: for each chromosome * one entry * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_fullModeAllowed array of boolean values. For each chromosome there * is one value indicating whether the full mode for creating chromosome * generations during evolution is allowed (true) or not (false) * @param a_verboseOutput true: output status information to console * * @return created genotype with initialized population * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public static GPGenotype randomInitialGenotype(final GPConfiguration a_conf, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, boolean[] a_fullModeAllowed, boolean a_verboseOutput) throws InvalidConfigurationException { return randomInitialGenotype(a_conf, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes, a_fullModeAllowed, a_verboseOutput, new DefaultPopulationCreator()); } /** * Allows to use a custom mechanism for population creation. * * @param a_conf the configuration to use * @param a_types the type of each chromosome, the length is the number of * chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each * chromosome, must be an array of arrays, the first dimension of which is the * number of chromosomes and the second dimension of which is the number of * nodes. Note that it is not necessary to include the arguments of a * chromosome as terminals in the chromosome's node set. This is done * automatically * @param a_minDepths array of minimum depths to use: for each chromosome * one entry * @param a_maxDepths array of maximum depths to use: for each chromosome * one entry * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_fullModeAllowed array of boolean values. For each chromosome there * is one value indicating whether the full mode for creating chromosome * generations during evolution is allowed (true) or not (false) * @param a_verboseOutput true: output status information to console * @param a_popCreator mechanism fior creating the population * * @return GPGenotype * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.2.2 */ public static GPGenotype randomInitialGenotype(final GPConfiguration a_conf, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, boolean[] a_fullModeAllowed, boolean a_verboseOutput, IPopulationCreator a_popCreator) throws InvalidConfigurationException { // Check preconditions. // -------------------- if (a_argTypes.length != a_fullModeAllowed.length || (a_minDepths != null && a_argTypes.length != a_minDepths.length) || (a_maxDepths != null && a_argTypes.length != a_maxDepths.length) || a_argTypes.length != a_types.length) { throw new IllegalArgumentException("a_argTypes must have same length as" + " a_types, a_minDepths, a_maxDepths and a_fullModeAllowed"); } if (a_conf.getPopulationSize() < 1) { throw new IllegalArgumentException("Set the population size in the" + " configuration!"); } // Clean up memory. // ---------------- System.gc(); // Determine which GP functions are never used as child. // ----------------------------------------------------- // Map<String, CommandGene> invalidNodes = verifyChildNodes(a_conf, a_types, // a_nodeSets); // outputWarning(invalidNodes); // Determine the depths for which GP functions are not possible. // ------------------------------------------------------------- // Map<CommandGene, int[]> invalidDepthNodes = verifyDepthsForNodes(a_conf, // a_types, a_minDepths, a_maxDepths, a_maxNodes, a_nodeSets); // outputDepthInfo(invalidDepthNodes); /**@todo implement*/ if (a_verboseOutput) { LOGGER.info("Creating initial population"); LOGGER.info("Mem free: " + SystemKit.niceMemory(SystemKit.getTotalMemoryMB()) + " MB"); } // Create initial population. // -------------------------- GPPopulation pop = new GPPopulation(a_conf, a_conf.getPopulationSize()); try { a_popCreator.initialize(pop, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes, a_fullModeAllowed); } catch (Exception ex) { throw new InvalidConfigurationException(ex); } System.gc(); if (a_verboseOutput) { LOGGER.info("Mem free after creating population: " + SystemKit.niceMemory(SystemKit.getTotalMemoryMB()) + " MB"); } checkErroneousPop(pop, " after creating population/2"); GPGenotype gp = new GPGenotype(a_conf, pop, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes); gp.m_fullModeAllowed = a_fullModeAllowed; // Publish GP variables to configuration to make them accessible globally. // ----------------------------------------------------------------------- Iterator it = gp.m_variables.keySet().iterator(); while (it.hasNext()) { /**@todo optimize access to map*/ String varName = (String) it.next(); Variable var = (Variable) gp.m_variables.get(varName); a_conf.putVariable(var); } gp.checkErroneousPop(gp.getGPPopulation(), " after creating population/2"); return gp; } public GPConfiguration getGPConfiguration() { return m_configuration; } /** * @return the static configuration to use with Genetic Programming * * @author Klaus Meffert * @since 3.2 */ public static GPConfiguration getStaticGPConfiguration() { return m_staticConfiguration; } /** * Sets the static configuration to use with the Genetic Programming. * * @param a_configuration the static configuration to use * * @author Klaus Meffert * @since 3.2 */ public static void setStaticGPConfiguration(GPConfiguration a_configuration) { m_staticConfiguration = a_configuration; } static class GPFitnessComparator implements Comparator { public int compare(Object o1, Object o2) { if (! (o1 instanceof IGPProgram) || ! (o2 instanceof IGPProgram)) throw new ClassCastException( "FitnessComparator must operate on IGPProgram instances"); double f1 = ( (IGPProgram) o1).getFitnessValue(); double f2 = ( (IGPProgram) o2).getFitnessValue(); if (f1 > f2) { return 1; } else if (Math.abs(f1 - f2) < 0.000001) { return 0; } else { return -1; } } } /** * Evolves the population n times. * * @param a_evolutions number of evolution * * @author Klaus Meffert * @since 3.0 */ public void evolve(int a_evolutions) { int offset = getGPConfiguration().getGenerationNr(); int evolutions; if (a_evolutions < 0) { evolutions = Integer.MAX_VALUE; } else { evolutions = a_evolutions; } // getGPPopulation().sort(new GPFitnessComparator()); for (int i = 0; i < evolutions; i++) { // if (m_bestFitness < 0.000001) { // // Optimal solution found, quit. // // ----------------------------- // return; // } if (m_verbose) { if (i % 25 == 0) { String freeMB = SystemKit.niceMemory(SystemKit.getFreeMemoryMB()); LOGGER.info("Evolving generation " + (i + offset) + ", memory free: " + freeMB + " MB"); } } evolve(); calcFitness(); } } /** * Calculates the fitness value of all programs, of the best solution as well * as the total fitness (sum of all fitness values). * * @author Klaus Meffert * @since 3.0 */ public void calcFitness() { double totalFitness = 0.0d; GPPopulation pop = getGPPopulation(); IGPProgram best = null; IGPFitnessEvaluator evaluator = getGPConfiguration().getGPFitnessEvaluator(); m_bestFitness = FitnessFunction.NO_FITNESS_VALUE; boolean bestPreserved = false; for (int i = 0; i < pop.size() && pop.getGPProgram(i) != null; i++) { IGPProgram program = pop.getGPProgram(i); /**@todo get information from fitness function how calculation happened. * In case of Robocode: return the robot competed against, in case the * -enemies option was used without -battleAll */ double fitness; try { fitness = program.getFitnessValue(); } catch (IllegalStateException iex) { fitness = Double.NaN; } // Don't acceppt Infinity or NaN as a result. // ------------------------------------------ if (Double.isInfinite(fitness) || Double.isNaN(fitness)) { continue; } if (best == null || evaluator.isFitter(fitness, m_bestFitness)) { best = program; m_bestFitness = fitness; if (!bestPreserved && m_allTimeBest != null) { if (best.toStringNorm(0).equals(m_allTimeBest.toStringNorm(0))) { bestPreserved = true; } } } totalFitness += fitness; } m_totalFitness = totalFitness; best = pop.determineFittestProgram(); if (best != null) { m_bestFitness = best.getFitnessValue(); /**@todo do something similar here as with Genotype.preserveFittestChromosome*/ if (m_allTimeBest == null || evaluator.isFitter(m_bestFitness, m_allTimeBestFitness)) { pop.setChanged(true); try { ICloneHandler cloner = getGPConfiguration().getJGAPFactory(). getCloneHandlerFor(best, null); if (cloner == null) { m_allTimeBest = best; if (!m_cloneWarningGPProgramShown) { LOGGER.info("Warning: cannot clone instance of " + best.getClass()); m_cloneWarningGPProgramShown = true; } } else { m_allTimeBest = (IGPProgram) cloner.perform(best, null, null); } } catch (Exception ex) { m_allTimeBest = best; ex.printStackTrace(); } m_allTimeBestFitness = m_bestFitness; // Fire an event to indicate a new best solution. // ---------------------------------------------- /**@todo introduce global value object to be passed to the listener*/ try { getGPConfiguration().getEventManager().fireGeneticEvent( new GeneticEvent(GeneticEvent.GPGENOTYPE_NEW_BEST_SOLUTION, this)); } catch (IllegalArgumentException iex) { /**@todo should not happen but does with ensureUniqueness(..)*/ } if (m_verbose) { // Output the new best solution found. // ----------------------------------- outputSolution(m_allTimeBest); } } } if (!bestPreserved && m_allTimeBest != null) { addFittestProgram(m_allTimeBest); } } /** * @return the all-time best solution found * * @author Klaus Meffert * @since 3.0 */ public IGPProgram getAllTimeBest() { return m_allTimeBest; } /** * Outputs the best solution until now. * * @param a_best the fittest ProgramChromosome * * @author Klaus Meffert * @since 3.0 */ public void outputSolution(IGPProgram a_best) { if (a_best == null) { LOGGER.debug("No best solution (null)"); return; } double bestValue = a_best.getFitnessValue(); if (Double.isInfinite(bestValue)) { LOGGER.debug("No best solution (infinite)"); return; } LOGGER.info("Best solution fitness: " + NumberKit.niceDecimalNumber(bestValue, 2)); LOGGER.info("Best solution: " + a_best.toStringNorm(0)); String depths = ""; int size = a_best.size(); for (int i = 0; i < size; i++) { if (i > 0) { depths += " / "; } depths += a_best.getChromosome(i).getDepth(0); } if (size == 1) { LOGGER.info("Depth of chrom: " + depths); } else { LOGGER.info("Depths of chroms: " + depths); } } /** * Evolve the population by one generation. Probabilistically reproduces * and crosses individuals into a new population which then overwrites the * original population. * * @author Klaus Meffert * @since 3.0 */ public void evolve() { try { int popSize = getGPConfiguration().getPopulationSize(); GPPopulation oldPop = getGPPopulation(); GPPopulation newPopulation = new GPPopulation(oldPop, false); if (m_fittestToAdd != null) { newPopulation.addFittestProgram(m_fittestToAdd); m_fittestToAdd = null; } float val; RandomGenerator random = getGPConfiguration().getRandomGenerator(); GPConfiguration conf = getGPConfiguration(); // Determine how many new individuals will be added to the new generation. // ----------------------------------------------------------------------- int popSize1 = (int) Math.round(popSize * (1 - conf.getNewChromsPercent())); double crossProb = conf.getCrossoverProb() / (conf.getCrossoverProb() + conf.getReproductionProb()); int crossover = 0; int reproduction = 0; int creation = 0; checkErroneousPop(getGPPopulation(), " (before evolution)", true); final int maxTries = getGPConfiguration().getProgramCreationMaxtries(); // Do crossing over. // ----------------- for (int i = 0; i < popSize1; i++) { // Clear the stack for each GP program. // ------------------------------------ getGPConfiguration().clearStack(); val = random.nextFloat(); // Note that if we only have one slot left to fill, we don't do // crossover, but fall through to reproduction. // ------------------------------------------------------------ if (i < popSize - 1 && val < crossProb) { crossover++; // Actually do the crossover here. // ------------------------------- IGPProgram i1 = conf.getSelectionMethod().select(this); IGPProgram i2 = conf.getSelectionMethod().select(this); int tries = 0; do { try { checkErroneousProg(i1, " at start of evolution (index " + i + "/01)", false); checkErroneousProg(i2, " at start of evolution (index " + i + "/02)", false); IGPProgram[] newIndividuals = conf.getCrossMethod().operate(i1, i2); newPopulation.setGPProgram(i, newIndividuals[0]); newPopulation.setGPProgram(i + 1, newIndividuals[1]); try { checkErroneousProg(newIndividuals[0], " at start of evolution (index " + i + "/11)", false); } catch (RuntimeException t) { writeToFile(i1, i2, newIndividuals[0], "Error in first X-over program"); throw t; } try { checkErroneousProg(newIndividuals[1], " at start of evolution (index " + i + "/12)", false); } catch (RuntimeException t) { writeToFile(i1, i2, newIndividuals[1], "Error in second X-over program"); throw t; } i++; break; } catch (IllegalStateException iex) { tries++; if ( (maxTries > 0 && tries >= maxTries) || tries > 40) { if (!getGPConfiguration().isMaxNodeWarningPrinted()) { LOGGER.error( "Warning: Maximum number of nodes allowed may be too small"); getGPConfiguration().flagMaxNodeWarningPrinted(); } // Try cloning a previously generated valid program. // ------------------------------------------------- IGPProgram program = cloneProgram(getGPConfiguration(). getPrototypeProgram()); if (program != null) { newPopulation.setGPProgram(i++, program); program = cloneProgram(getGPConfiguration(). getPrototypeProgram()); newPopulation.setGPProgram(i, program); break; } else { throw new IllegalStateException(iex.getMessage()); } } } } while (true) ; } else { //if (val < conf.getCrossoverProb() + conf.getReproductionProb()) { // Reproduction only. // ------------------ reproduction++; newPopulation.setGPProgram(i, conf.getSelectionMethod().select(this)); } } // Add new random programs. // ------------------------ for (int i = popSize1; i < popSize; i++) { creation++; // Randomly determine depth between minInitDepth and maxInitDepth. // --------------------------------------------------------------- int depth = conf.getMinInitDepth() + random.nextInt(conf.getMaxInitDepth() - conf.getMinInitDepth() + 1); int tries = 0; do { try { // Randomize grow option as growing produces a valid program // more likely than the full mode. // --------------------------------------------------------- boolean grow; if (i % 2 == 0 || random.nextInt(8) > 6) { grow = true; } else { grow = false; } /**@todo use program creator in case such is registered and returns * a non-null program */ IGPProgram program = newPopulation.create(i, m_types, m_argTypes, m_nodeSets, m_minDepths, m_maxDepths, depth, grow, m_maxNodes, m_fullModeAllowed, tries); newPopulation.setGPProgram(i, program); checkErroneousProg(program, " when adding a program, evolution (index " + i + ")", true); LOGGER.debug("Added new GP program (depth parameter: " + depth + ", " + tries + " tries)"); break; } catch (IllegalStateException iex) { tries++; /**@todo instead of re-using prototype, create a program anyway * (ignoring the validator) in case it is the last try. * Or even better: Make the validator return a defect rate! */ if ( (maxTries > 0 && tries > maxTries) || tries > 40) { LOGGER.debug( "Creating random GP program failed (depth " + depth + ", " + tries + " tries), will use prototype"); // Try cloning a previously generated valid program. // ------------------------------------------------- IGPProgram program = cloneProgram(getGPConfiguration(). getPrototypeProgram()); if (program != null) { // Cloning worked. // --------------- newPopulation.setGPProgram(i, program); break; } else { if (getGPConfiguration().getPrototypeProgram() == null) { throw new IllegalStateException( "Cloning: Prototype program was null"); } else { throw new IllegalStateException( "Cloning of prototype program failed, " + iex.getMessage()); } } } } } while (true) ; } LOGGER.debug("Did " + crossover + " x-overs, " + reproduction + " reproductions, " + creation + " creations"); // Now set the new population as the active one. // --------------------------------------------- setGPPopulation(newPopulation); // Increase number of generation. // ------------------------------ conf.incrementGenerationNr(); // Fire an event to indicate we've performed an evolution. // ------------------------------------------------------- conf.getEventManager().fireGeneticEvent( new GeneticEvent(GeneticEvent.GPGENOTYPE_EVOLVED_EVENT, this)); } catch (InvalidConfigurationException iex) { // This should never happen. // ------------------------- throw new IllegalStateException(iex.getMessage()); } } public GPPopulation getGPPopulation() { return m_population; } /** * @return the total fitness, that is the fitness over all chromosomes * * @author Klaus Meffert * @since 3.0 */ public double getTotalFitness() { return m_totalFitness; } /** * Default implementation of method to run GPGenotype as a thread. * * @author Klaus Meffert * @since 3.0 */ public void run() { try { while (!Thread.currentThread().interrupted()) { evolve(); calcFitness(); // Pause between evolutions to avoid 100% CPU load. // ------------------------------------------------ Thread.sleep(10); } } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } } /** * Retrieves the GPProgram in the population with the highest fitness * value. * * @return the GPProgram with the highest fitness value, or null if there * are no programs in this Genotype * * @author Klaus Meffert * @since 3.0 */ public synchronized IGPProgram getFittestProgram() { double fittest; if (m_allTimeBest != null) { fittest = m_allTimeBest.getFitnessValue(); } else { fittest = FitnessFunction.NO_FITNESS_VALUE; } IGPProgram fittestPop = getGPPopulation().determineFittestProgram(); if (fittestPop == null) { return m_allTimeBest; } if (getGPConfiguration().getGPFitnessEvaluator().isFitter(fittest, fittestPop.getFitnessValue())) { return m_allTimeBest; } else { // m_allTimeBest = fittestPop;/**@todo*/ return fittestPop; } } /** * Retrieves the GPProgram in the population with the highest fitness * value. Only considers programs for which the fitness value has already * been computed. * * @return the GPProgram with the highest fitness value, or null if there * are no programs with known fitness value in this Genotype * * @author Klaus Meffert * @since 3.2 */ public synchronized IGPProgram getFittestProgramComputed() { return getGPPopulation().determineFittestProgramComputed(); } protected void setGPPopulation(GPPopulation a_pop) { m_population = a_pop; } /** * Sets the configuration to use with the Genetic Algorithm. * @param a_configuration the configuration to use * * @author Klaus Meffert * @since 3.0 */ public void setGPConfiguration(GPConfiguration a_configuration) { m_configuration = a_configuration; } /** * Compares this entity against the specified object. * * @param a_other the object to compare against * @return true: if the objects are the same, false otherwise * * @author Klaus Meffert * @since 3.0 */ public boolean equals(Object a_other) { try { return compareTo(a_other) == 0; } catch (ClassCastException cex) { return false; } } /** * Compares this Genotype against the specified object. The result is true * if the argument is an instance of the Genotype class, has exactly the * same number of programs as the given Genotype, and, for each * GPProgram in this Genotype, there is an equal program in the * given Genotype. The programs do not need to appear in the same order * within the populations. * * @param a_other the object to compare against * @return a negative number if this genotype is "less than" the given * genotype, zero if they are equal to each other, and a positive number if * this genotype is "greater than" the given genotype * * @author Klaus Meffert * @since 3.0 */ public int compareTo(Object a_other) { try { // First, if the other Genotype is null, then they're not equal. // ------------------------------------------------------------- if (a_other == null) { return 1; } GPGenotype otherGenotype = (GPGenotype) a_other; // First, make sure the other Genotype has the same number of // chromosomes as this one. // ---------------------------------------------------------- int size1 = getGPPopulation().size(); int size2 = otherGenotype.getGPPopulation().size(); if (size1 != size2) { if (size1 > size2) { return 1; } else { return -1; } } // Next, prepare to compare the programs of the other Genotype // against the programs of this Genotype. To make this a lot // simpler, we first sort the programs in both this Genotype // and the one we're comparing against. This won't affect the // genetic algorithm (it doesn't care about the order), but makes // it much easier to perform the comparison here. // -------------------------------------------------------------- Arrays.sort(getGPPopulation().getGPPrograms()); Arrays.sort(otherGenotype.getGPPopulation().getGPPrograms()); for (int i = 0; i < getGPPopulation().size(); i++) { int result = (getGPPopulation().getGPProgram(i).compareTo( otherGenotype.getGPPopulation().getGPProgram(i))); if (result != 0) { return result; } } return 0; } catch (ClassCastException e) { return -1; } } /*** * Hashcode function for the genotype, tries to create a unique hashcode for * the chromosomes within the population. The logic for the hashcode is * * Step Result * ---- ------ * 1 31*0 + hashcode_0 = y(1) * 2 31*y(1) + hashcode_1 = y(2) * 3 31*y(2) + hashcode_2 = y(3) * n 31*y(n-1) + hashcode_n-1 = y(n) * * Each hashcode is a number and the binary equivalent is computed and * returned. * @return the computed hashcode * * @author Klaus Meffert * @since 3.0 */ public int hashCode() { int i, size = getGPPopulation().size(); IGPProgram prog; int twopower = 1; // For empty genotype we want a special value different from other hashcode // implementations. // ------------------------------------------------------------------------ int localHashCode = -573; for (i = 0; i < size; i++, twopower = 2 * twopower) { prog = getGPPopulation().getGPProgram(i); localHashCode = 31 * localHashCode + prog.hashCode(); } return localHashCode; } /** * @param a_verbose true: output status information to console * * @author Klaus Meffert * @since 3.0 */ public void setVerboseOutput(boolean a_verbose) { m_verbose = a_verbose; } private IGPProgram cloneProgram(IGPProgram a_original) { IGPProgram validProgram = a_original; ICloneHandler cloner = getGPConfiguration().getJGAPFactory(). getCloneHandlerFor(validProgram, null); if (cloner != null) { try { IGPProgram program = (IGPProgram) cloner.perform( validProgram, null, null); return program; } catch (Exception ex) { LOGGER.error(ex.getMessage(), ex); return null; } } return null; } /** * Stores a Variable. * * @param a_var the Variable to store * * @author Klaus Meffert * @since 3.2 */ public void putVariable(Variable a_var) { m_variables.put(a_var.getName(), a_var); } /** * @param a_varName name of variable to retriebe * @return Variable instance or null, if not found * * @author Klaus Meffert * @since 3.2 */ public Variable getVariable(String a_varName) { return (Variable) m_variables.get(a_varName); } /** * Adds a GP program to this Genotype. Does nothing when given null. * The injection is actually executed in method create(..) of GPPopulation. * * @param a_toAdd the program to add * * @author Klaus Meffert * @since 3.2 */ public void addFittestProgram(final IGPProgram a_toAdd) { if (a_toAdd != null) { m_fittestToAdd = a_toAdd; } } /** * Fills up the population with random programs if necessary. * * @param a_num the number of programs to add * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.2 */ public void fillPopulation(final int a_num) throws InvalidConfigurationException { IGPProgram sampleProg = getGPConfiguration().getPrototypeProgram(); if (sampleProg == null) { /**@todo care about*/ } Class sampleClass = sampleProg.getClass(); IInitializer chromIniter = getGPConfiguration().getJGAPFactory(). getInitializerFor(sampleProg, sampleClass); if (chromIniter == null) { throw new InvalidConfigurationException("No initializer found for class " + sampleClass); } try { for (int i = 0; i < a_num; i++) { /**@todo implement filling up population*/ // getGPPopulation().addChromosome( (IChromosome) chromIniter.perform(sampleProg, // sampleClass, null)); } } catch (Exception ex) { throw new IllegalStateException(ex); } } // /** // * Disabling a chromosome is equivalent to not declaring it. However, if you // * skip a declaration, indices will shift. With this method it is easier // * skipping a chromosome. // * // * @param a_index index of the chromosome to disable. // * // * @author Klaus Meffert // * @since 3.2.2 // */ // public void disableChromosome(int a_index) { // if (a_index < 0 || a_index >= disabledChromosomes.length) { // throw new IllegalArgumentException("Invalid index!"); // } // disabledChromosomes[a_index] = true; // } // // /** // * // * @param a_index index of the chromosome to check. // * @return true if chromosome disabled // * // * @author Klaus Meffert // * @since 3.2.2 // */ // public boolean isDisabledChromosome(int a_index) { // if (a_index < 0 || a_index >= disabledChromosomes.length) { // throw new IllegalArgumentException("Invalid index!"); // } // return disabledChromosomes[a_index]; // } public static void checkErroneousPop(GPPopulation pop, String s) { checkErroneousPop(pop, s, false); } public static void checkErroneousPop(GPPopulation a_pop, String a_s, boolean a_clearFitness) { if (a_pop == null) { return; } checkErroneousPop(a_pop, a_s, a_clearFitness, a_pop.getGPConfiguration().isVerifyPrograms()); } public static void checkErroneousPop(GPPopulation a_pop, String a_s, boolean a_clearFitness, boolean a_active) { if (a_pop == null) { return; } if (!a_active) { // Verification not activated! // --------------------------- return; } int popSize1 = a_pop.size(); for (int i = 0; i < popSize1; i++) { IGPProgram a_prog = a_pop.getGPProgram(i); checkErroneousProg(a_prog, a_s, a_clearFitness, a_active); } } public static void checkErroneousProg(IGPProgram prog, String s) { checkErroneousProg(prog, s, false); } public static void checkErroneousProg(IGPProgram a_prog, String a_s, boolean a_clearFitness) { if (a_prog == null) { return; } checkErroneousProg(a_prog, a_s, a_clearFitness, a_prog.getGPConfiguration().isVerifyPrograms()); } public static void checkErroneousProg(IGPProgram a_prog, String s, boolean a_clearFitness, boolean a_active) { if (a_prog == null) { return; } if (!a_active) { // Verification not activated! // --------------------------- return; } // Has program already been verified? // ---------------------------------- /**@todo impl. cache*/ if (a_clearFitness) { // Reset fitness value. // -------------------- a_prog.setFitnessValue(GPFitnessFunction.NO_FITNESS_VALUE); } try { a_prog.getFitnessValue(); } catch (Throwable ex) { String msg = "Invalid program detected" + s + "!"; LOGGER.fatal(msg); throw new RuntimeException(msg, ex); } } /** * Write three GP programs being involved in crossover as a string to a file * for debug purposes. * * @param i1 first program * @param i2 second program * @param inew resulting program * @param header text for header text * * @author Klaus Meffert */ private void writeToFile(IGPProgram i1, IGPProgram i2, IGPProgram inew, String header) { StringBuffer sb = new StringBuffer(); try { sb.append(header); sb.append("First Program to cross over\n"); sb.append(getProgramString(i1)); sb.append("\nSecond Program to cross over\n"); sb.append(getProgramString(i2)); sb.append("\nResulting Program after cross over\n"); sb.append(getProgramString(inew)); String filename = DateKit.getNowAsString(); File f = new File(filename); FileWriter fw = new FileWriter(f); fw.write(sb.toString()); fw.close(); } catch (IOException iex) { System.out.println(sb.toString()); iex.printStackTrace(); } } /** * Utility function. * * @param i1 program to get textual representation for * @return textual representation of the given program * * @author Klaus Meffert */ private StringBuffer getProgramString(IGPProgram i1) { int size = i1.size(); int size2; ProgramChromosome chrom; CommandGene gene; StringBuffer result = new StringBuffer(); for (int i = 0; i < size; i++) { chrom = i1.getChromosome(i); result.append("Chromosome " + i + ", class " + chrom.getClass() + "\n"); size2 = chrom.size(); for (int j = 0; j < size2; j++) { gene = chrom.getGene(j); result.append("Gene " + j + ", class " + gene.getClass() + ", toString: " + gene.toString() + " \n"); } } return result; } /** * * @param a_conf GPConfiguration * @param a_types Class[] * @param a_nodeSets CommandGene[][] * @return Map * * @author Klaus Meffert * @since 3.4.4 */ public static Map<String, CommandGene> verifyChildNodes( GPConfiguration a_conf, Class[] a_types, CommandGene[][] a_nodeSets) { List<CommandGene> triedNodes; Map<String, CommandGene> invalidNodes = new Hashtable(); // For every chromosome in the GP program. // --------------------------------------- for (int i = 0; i < a_nodeSets.length; i++) { triedNodes = new Vector(); // Determine impossible functions and terminals. // --------------------------------------------- for (int j = 0; j < a_nodeSets[i].length; j++) { CommandGene node = a_nodeSets[i][j]; if (triedNodes.contains(node)) { continue; } triedNodes.add(node); // Verify if node is possible. // --------------------------- int arity = node.getArity(null); Class[] childTypes; int[] subChildTypes; if (arity > 0) { childTypes = new Class[arity]; for (int k = 0; k < arity; k++) { childTypes[k] = node.getChildType(null, k); } if (node.getSubChildTypes() != null) { subChildTypes = new int[arity]; for (int k = 0; k < arity; k++) { subChildTypes[k] = node.getSubChildType(k); } } else { subChildTypes = new int[arity]; } } else { childTypes = null; subChildTypes = null; } if (arity > 0) { // Is there any other node fitting as a child for the currently // examined node? // ------------------------------------------------------------ for (int l = 0; l < arity; l++) { if (!nodeExists(a_nodeSets[i], childTypes[l], subChildTypes[l])) { String s = i + "," + j; invalidNodes.put(s, node); break; } } } } } return invalidNodes; } /** * Is there a node with the needed return type and sub return type within the * given function set? * * @param a_functionSet collection of available nodes * @param a_returnType needed return type * @param a_subReturnType needed sub return type * @return true: node found * * @author Klaus Meffert * @since 3.4.4 */ protected static boolean nodeExists(CommandGene[] a_functionSet, Class a_returnType, int a_subReturnType) { for (int i = 0; i < a_functionSet.length; i++) { if (a_functionSet[i].getReturnType() == a_returnType && (a_subReturnType == 0 || a_subReturnType == a_functionSet[i].getSubReturnType())) { return true; } } return false; } /** * Outputs the nodes that are never used. * * @param invalidNodes never used nodes * * @author Klaus Meffert * @since 3.4.4 */ protected static void outputWarning(Map<String, CommandGene> invalidNodes) { if (invalidNodes != null && invalidNodes.size() > 0) { LOGGER.warn("Your configuration contains commands that are not used:"); Iterator it = invalidNodes.values().iterator(); while (it.hasNext()) { CommandGene node = (CommandGene) it.next(); LOGGER.warn(" " + node.getClass().getName()); } } else { LOGGER.info("Your configuration does not contain unused commands," + " this is good"); } } /** * Outputs the nodes that are never used. * * @param a_invalidDepths impossible depths for nodes * * @author Klaus Meffert * @since 3.4.4 */ protected static void outputDepthInfo(Map<CommandGene, int[]> a_invalidDepths) { if (a_invalidDepths != null && a_invalidDepths.size() > 0) { LOGGER.info("Your configuration contains commands that are not possible" + "for certain depths : "); Iterator it = a_invalidDepths.keySet().iterator(); while (it.hasNext()) { CommandGene node = (CommandGene) it.next(); LOGGER.info(" " + node.getClass().getName()); int[] depths = (int[]) a_invalidDepths.get(node); String s = ""; for (int i = 0; i < depths.length; i++) { if (i > 0) { s += ", "; } s += depths[i]; } LOGGER.info(" Impossible depths: " + s); } } } public static Map<CommandGene, int[]> verifyDepthsForNodes( GPConfiguration a_conf, Class[] a_types, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, CommandGene[][] a_nodeSets) { List<CommandGene> triedNodes; List<CommandGene> possibleNodes; Map<CommandGene, int[]> impossibleDepths = new Hashtable(); for (int i = 0; i < a_nodeSets.length; i++) { // Determine impossible functions and terminals. // --------------------------------------------- for (int j = 0; j < a_nodeSets[i].length; j++) { CommandGene nodeToCheck = a_nodeSets[i][j]; triedNodes = new Vector(); // Determine all possible nodes for current node. // ---------------------------------------------- possibleNodes = new Vector(); /**@todo impl*/ for (int k = 0; k < a_nodeSets[i].length; j++) { CommandGene nodeInQuestion = a_nodeSets[i][k]; if (triedNodes.contains(nodeInQuestion)) { continue; } boolean valid = false; for(int l=0;l<nodeToCheck.size();l++) { if (nodeInQuestion.getReturnType() == nodeToCheck.getChildType(null, l)) { if (nodeInQuestion.getSubReturnType() == 0 || nodeInQuestion.getSubReturnType() == nodeToCheck.getSubChildType(l)) { valid = true; break; } } } if (valid) { possibleNodes.add(nodeInQuestion); } triedNodes.add(nodeInQuestion); } // Determine possible depths for each node. // Do not consider nodes already in progress. // ------------------------------------------ /**@todo impl*/ } } return impossibleDepths; } }