/*
* 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;
import java.util.*;
import org.jgap.*;
/**
* The Greedy Crossover is a specific type of crossover. It can only be is
* applied if
* <ul>
* <li>
* 1. All genes in the chromosome are different and
* </li>
* <li>
* 2. The set of genes for both chromosomes is identical and only their order
* in the chromosome can vary.
* </li>
* </ul>
*
* After the GreedyCrossover, these two conditions always remain true, so
* it can be applied again and again.
*
* The algorithm throws an assertion error if the two initial chromosomes
* does not satisfy these conditions.
*
* Greedy crossover can be best explained in the terms of the
* Traveling Salesman Problem:
*
* The algorithm selects the first city of one parent, compares the cities
* leaving that city in both parents, and chooses the closer one to extend
* the tour. If one city has already appeared in the tour, we choose the
* other city. If both cities have already appeared, we randomly select a
* non-selected city.
*
* See 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.
* and also <a href="http://ecsl.cs.unr.edu/docs/techreports/gong/node3.html">
* Sushil J. Louis & Gong Li</a>}
*
* @author Audrius Meskauskas
* @author <font size=-1>Neil Rotstan, Klaus Meffert (reused code
* from {@link org.jgap.impl.CrossoverOperator CrossoverOperator})</font>
* @since 2.0
*/
public class GreedyCrossover
extends BaseGeneticOperator {
/** String containing the CVS revision. Read out via reflection!*/
private static final String CVS_REVISION = "$Revision: 1.30 $";
/** Switches assertions on/off. Must be true during tests and debugging. */
boolean ASSERTIONS = true;
private int m_startOffset = 1;
/**
* Default constructor for dynamic instantiation.<p>
* Attention: The configuration used is the one set with the static method
* Genotype.setConfiguration.
*
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 2.6
* @since 3.0 (since 2.0 without a_configuration)
*/
public GreedyCrossover()
throws InvalidConfigurationException {
super(Genotype.getStaticConfiguration());
}
/**
* Using the given configuration.
*
* @param a_configuration the configuration to use
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 3.0 (since 2.6 without a_configuration)
*/
public GreedyCrossover(Configuration a_configuration)
throws InvalidConfigurationException {
super(a_configuration);
}
/**
* Compute the distance between "cities", indicated by these two
* given genes. The default method expects the genes to be
* IntegerGene's and returns their absolute difference, that
* makes sense only for tests.
*
* @param a_from Object
* @param a_to Object
* @return distance between the two given cities
*/
public double distance(final Object a_from, final Object a_to) {
IntegerGene from = (IntegerGene) a_from;
IntegerGene to = (IntegerGene) a_to;
return Math.abs(to.intValue() - from.intValue());
}
public void operate(final Population a_population,
final List a_candidateChromosomes) {
int size = Math.min(getConfiguration().getPopulationSize(),
a_population.size());
int numCrossovers = size / 2;
RandomGenerator generator = getConfiguration().getRandomGenerator();
// For each crossover, grab two random chromosomes and do what
// Grefenstette et al say.
// --------------------------------------------------------------
for (int i = 0; i < numCrossovers; i++) {
IChromosome origChrom1 = a_population.getChromosome(generator.
nextInt(size));
IChromosome firstMate = (IChromosome)origChrom1.clone();
IChromosome origChrom2 = a_population.getChromosome(generator.
nextInt(size));
IChromosome secondMate = (IChromosome)origChrom2.clone();
// In case monitoring is active, support it.
// -----------------------------------------
if (m_monitorActive) {
firstMate.setUniqueIDTemplate(origChrom1.getUniqueID(), 1);
firstMate.setUniqueIDTemplate(origChrom2.getUniqueID(), 2);
secondMate.setUniqueIDTemplate(origChrom1.getUniqueID(), 1);
secondMate.setUniqueIDTemplate(origChrom2.getUniqueID(), 2);
}
operate(firstMate, secondMate);
// Add the modified chromosomes to the candidate pool so that
// they'll be considered for natural selection during the next
// phase of evolution.
// -----------------------------------------------------------
a_candidateChromosomes.add(firstMate);
a_candidateChromosomes.add(secondMate);
}
}
/**
* Performs a greedy crossover for the two given chromosoms.
*
* @param a_firstMate the first chromosome to crossover on
* @param a_secondMate the second chromosome to crossover on
* @throws Error if the gene set in the chromosomes is not identical
*
* @author Audrius Meskauskas
* @since 2.1
*/
public void operate(final IChromosome a_firstMate,
final IChromosome a_secondMate) {
Gene[] g1 = a_firstMate.getGenes();
Gene[] g2 = a_secondMate.getGenes();
Gene[] c1, c2;
try {
c1 = operate(g1, g2);
c2 = operate(g2, g1);
a_firstMate.setGenes(c1);
a_secondMate.setGenes(c2);
}
catch (InvalidConfigurationException cex) {
throw new Error("Error occured while operating on:"
+ a_firstMate + " and "
+ a_secondMate
+ ". First " + m_startOffset + " genes were excluded "
+ "from crossover. Error message: "
+ cex.getMessage());
}
}
protected Gene[] operate(final Gene[] a_g1, final Gene[] a_g2) {
int n = a_g1.length;
LinkedList out = new LinkedList();
TreeSet not_picked = new TreeSet();
out.add(a_g1[m_startOffset]);
for (int j = m_startOffset + 1; j < n; j++) { // g[m_startOffset] picked
if (ASSERTIONS && not_picked.contains(a_g1[j])) {
throw new Error("All genes must be different for "
+ getClass().getName()
+ ". The gene " + a_g1[j] + "[" + j
+ "] occurs more "
+ "than once in one of the chromosomes. ");
}
not_picked.add(a_g1[j]);
}
if (ASSERTIONS) {
if (a_g1.length != a_g2.length) {
throw new Error("Chromosome sizes must be equal");
}
for (int j = m_startOffset; j < n; j++) {
if (!not_picked.contains(a_g2[j])) {
if (!a_g1[m_startOffset].equals(a_g2[j])) {
throw new Error("Chromosome gene sets must be identical."
+ " First gene set: " + a_g1
+ ", second gene set: " + a_g2);
}
}
}
}
while (not_picked.size() > 1) {
Gene last = (Gene) out.getLast();
Gene n1 = findNext(a_g1, last);
Gene n2 = findNext(a_g2, last);
Gene picked, other;
boolean pick1;
if (n1 == null) {
pick1 = false;
}
else if (n2 == null) {
pick1 = true;
}
else {
pick1 = distance(last, n1) < distance(last, n2);
}
if (pick1) {
picked = n1;
other = n2;
}
else {
picked = n2;
other = n1;
}
if (out.contains(picked)) {
picked = other;
}
if (picked == null || out /* still */.contains(picked)) {
// select a non-selected // it is not random
picked = (Gene) not_picked.first();
}
out.add(picked);
not_picked.remove(picked);
}
if (ASSERTIONS && not_picked.size() != 1) {
throw new Error("Given Gene not correctly created (must have length > 1"
+ ")");
}
out.add(not_picked.last());
Gene[] g = new Gene[n];
Iterator gi = out.iterator();
for (int i = 0; i < m_startOffset; i++) {
g[i] = a_g1[i];
}
if (ASSERTIONS) {
if (out.size() != g.length - m_startOffset) {
throw new Error("Unexpected internal error. "
+ "These two must be equal: " + out.size()
+ " and " + (g.length - m_startOffset) + ", g.length "
+ g.length + ", start offset " + m_startOffset);
}
}
for (int i = m_startOffset; i < g.length; i++) {
g[i] = (Gene) gi.next();
}
return g;
}
protected Gene findNext(final Gene[] a_g, final Gene a_x) {
for (int i = m_startOffset; i < a_g.length - 1; i++) {
if (a_g[i].equals(a_x)) {
return a_g[i + 1];
}
}
return null;
}
/**
* 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 the start offset to use
*/
public void setStartOffset(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 the start offset used
*/
public int getStartOffset() {
return m_startOffset;
}
/**
* Compares the given GeneticOperator to this GeneticOperator.
*
* @param a_other the instance against which to compare this instance
* @return a negative number if this instance is "less than" the given
* instance, zero if they are equal to each other, and a positive number if
* this is "greater than" the given instance
*
* @author Klaus Meffert
* @since 2.6
*/
public int compareTo(final Object a_other) {
if (a_other == null) {
return 1;
}
GreedyCrossover op = (GreedyCrossover) a_other;
if (getStartOffset() < op.getStartOffset()) {
// start offset less, meaning more to do --> return 1 for "is greater than"
return 1;
}
else if (getStartOffset() > op.getStartOffset()) {
return -1;
}
else {
// Everything is equal. Return zero.
// ---------------------------------
return 0;
}
}
}