/* * 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.impl.salesman; import java.io.*; import org.jgap.*; import org.jgap.event.*; import org.jgap.impl.*; /** * The class solves the travelling salesman problem. * The traveling salesman problem, or TSP for short, is this: given a finite * number of 'cities' along with the cost of travel between each pair of * them, find the cheapest way of visiting all the cities and returning to * your starting point.) * * @author Audrius Meskauskas * @author Neil Rotstan, Klaus Meffert (reused code fragments) * @since 2.0 * * @see * <ul> * <li>J. Grefenstette, R. Gopal, R. Rosmaita, and D. Gucht. * <i>Genetic algorithms for the traveling salesman problem</i>. * In Proceedings of the Second International Conference on Genetic * Algorithms. Lawrence Eribaum Associates, Mahwah, NJ, 1985. * </li> * <li> * <a href="http://ecsl.cs.unr.edu/docs/techreports/gong/node3.html"> * Sushil J. Louis & Gong Li</a> (explanatory material) * </li> * <li> * <a href="http://www.tsp.gatech.edu www.tsp.gatech.edu">TPS web site</a> * </li> * </ul> */ public abstract class Salesman implements Serializable { /** String containing the CVS revision. Read out via reflection!*/ private final static String CVS_REVISION = "$Revision: 1.22 $"; private Configuration m_config; private int m_maxEvolution = 128; private int m_populationSize = 512; /** * Override this method to compute the distance between "cities", * indicated by these two given genes. The algorithm is not dependent * on the used type of genes. * * @param a_from first gene, representing a city * @param a_to second gene, representing a city * @return the distance between two cities represented as genes * * @author Audrius Meskauskas * @since 2.0 */ public abstract double distance(Gene a_from, Gene a_to); /** * Override this method to create a single sample chromosome, representing * a list of "cities". Each gene corresponds a single "city" and * can appear only once. By default, the first gene corresponds * a "city" where the salesman starts the journey. * It never changes its position. This can be changed by setting other * start offset with setStartOffset( ). Other genes will be shuffled to * create the initial random population. * * @param a_initial_data the same object as was passed to findOptimalPath. * It can be used to specify the task more precisely if the class is * used for solving multiple tasks * @return a sample chromosome * * @author Audrius Meskauskas * @since 2.0 */ public abstract IChromosome createSampleChromosome(Object a_initial_data); /** * Return the fitness function to use. * * @param a_initial_data the same object as was passed to findOptimalPath. * It can be used to specify the task more precisely if the class is * used for solving multiple tasks * @return an applicable fitness function * * @author Audrius Meskauskas * @since 2.0 */ public FitnessFunction createFitnessFunction(final Object a_initial_data) { return new SalesmanFitnessFunction(this); } /** * Create a configuration. The configuration should not contain * operators for odrinary crossover and mutations, as they make * chromosoms invalid in this task. The special operators * SwappingMutationOperator and GreedyCrossober should be used instead. * * @param a_initial_data the same object as was passed to findOptimalPath. * It can be used to specify the task more precisely if the class is * used for solving multiple tasks * * @return created configuration * * @throws InvalidConfigurationException * * @author Audrius Meskauskas * @since 2.0 */ public Configuration createConfiguration(final Object a_initial_data) throws InvalidConfigurationException { // This is copied from DefaultConfiguration. // ----------------------------------------- Configuration config = new Configuration(); BestChromosomesSelector bestChromsSelector = new BestChromosomesSelector(config, 1.0d); bestChromsSelector.setDoubletteChromosomesAllowed(false); config.addNaturalSelector(bestChromsSelector, true); config.setRandomGenerator(new StockRandomGenerator()); config.setMinimumPopSizePercent(0); config.setEventManager(new EventManager()); config.setFitnessEvaluator(new DefaultFitnessEvaluator()); config.setChromosomePool(new ChromosomePool()); // These are different: // -------------------- config.addGeneticOperator(new GreedyCrossover(config)); config.addGeneticOperator(new SwappingMutationOperator(config, 20)); return config; } /** * @return maximal number of iterations for population to evolve * * @author Audrius Meskauskas * @since 2.0 */ public int getMaxEvolution() { return m_maxEvolution; } /** Set the maximal number of iterations for population to evolve * (default 512). * @param a_maxEvolution sic * * @author Audrius Meskauskas * @since 2.0 */ public void setMaxEvolution(final int a_maxEvolution) { m_maxEvolution = a_maxEvolution; } /** * @return population size for this solution * * @since 2.0 */ public int getPopulationSize() { return m_populationSize; } /** * Set an population size for this solution (default 512) * * @param a_populationSize sic * * @since 2.0 */ public void setPopulationSize(final int a_populationSize) { m_populationSize = a_populationSize; } /** * Executes the genetic algorithm to determine the * optimal path between the cities. * * @param a_initial_data can be a record with fields, specifying the * task more precisely if the class is used to solve multiple tasks. * It is passed to createFitnessFunction, createSampleChromosome and * createConfiguration * * @throws Exception * @return chromosome representing the optimal path between cities * * @author Audrius Meskauskas * @since 2.0 */ public IChromosome findOptimalPath(final Object a_initial_data) throws Exception { m_config = createConfiguration(a_initial_data); FitnessFunction myFunc = createFitnessFunction(a_initial_data); m_config.setFitnessFunction(myFunc); // Now we need to tell the Configuration object how we want our // Chromosomes to be setup. We do that by actually creating a // sample Chromosome and then setting it on the Configuration // object. // -------------------------------------------------------------- IChromosome sampleChromosome = createSampleChromosome(a_initial_data); m_config.setSampleChromosome(sampleChromosome); // Finally, we need to tell the Configuration object how many // Chromosomes we want in our population. The more Chromosomes, // the larger number of potential solutions (which is good for // finding the answer), but the longer it will take to evolve // the population (which could be seen as bad). We'll just set // the population size to 500 here. // ------------------------------------------------------------ m_config.setPopulationSize(getPopulationSize()); // Create random initial population of Chromosomes. // ------------------------------------------------ // As we cannot allow the normal mutations if this task, // we need multiple calls to createSampleChromosome. // ----------------------------------------------------- IChromosome[] chromosomes = new IChromosome[m_config.getPopulationSize()]; Gene[] samplegenes = sampleChromosome.getGenes(); for (int i = 0; i < chromosomes.length; i++) { Gene[] genes = new Gene[samplegenes.length]; for (int k = 0; k < genes.length; k++) { genes[k] = samplegenes[k].newGene(); genes[k].setAllele(samplegenes[k].getAllele()); } chromosomes[i] = new Chromosome(m_config, genes); } // Create the genotype. We cannot use Genotype.randomInitialGenotype, // Because we need unique gene values (representing the indices of the // cities of our problem). // ------------------------------------------------------------------- Genotype population = new Genotype(m_config, new Population(m_config, chromosomes)); IChromosome best = null; // Evolve the population. Since we don't know what the best answer // is going to be, we just evolve the max number of times. // --------------------------------------------------------------- Evolution: for (int i = 0; i < getMaxEvolution(); i++) { population.evolve(); best = population.getFittestChromosome(); } // Return the best solution we found. // ---------------------------------- return best; } private int m_startOffset = 1; /** * Sets a number of genes at the start of chromosome, that are * excluded from the swapping. In the Salesman task, the first city * in the list should (where the salesman leaves from) probably should * not change as it is part of the list. The default value is 1. * * @param a_offset start offset for chromosome * * @since 2.0 */ public void setStartOffset(final int a_offset) { m_startOffset = a_offset; } /** * Gets a number of genes at the start of chromosome, that are * excluded from the swapping. In the Salesman task, the first city * in the list should (where the salesman leaves from) probably should * not change as it is part of the list. The default value is 1. * * @return start offset for chromosome * * @since 2.0 */ public int getStartOffset() { return m_startOffset; } public Configuration getConfiguration() { return m_config; } }