/*
* 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;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import org.jgap.audit.*;
import org.jgap.util.*;
/**
* Makes up the population of a generation during evolution. Represented by a
* list of chromosomes held in the Genotype.
*
* @author Klaus Meffert
* @since 2.0
*/
public class Population
implements Serializable, ICloneable, IPersistentRepresentation {
/** String containing the CVS revision. Read out via reflection!*/
private static final String CVS_REVISION = "$Revision: 1.66 $";
/**
* The array of Chromosomes that makeup the Genotype's population.
*/
private List<IChromosome> m_chromosomes;
/**
* The fittest Chromosome of the population.
*/
private IChromosome m_fittestChromosome;
/**
* Indicates whether at least one of the chromosomes has been changed
* (deleted, added, modified).
*/
private boolean m_changed;
/**
* Indicates that the list of Chromosomes has been sorted.
*/
private boolean m_sorted;
private Configuration m_config;
public final static String CHROM_DELIMITER = "~";
/**
* Represents the heading delimiter that is used to separate chromosomes in
* the persistent representation.
*/
public final static String CHROM_DELIMITER_HEADING = "[";
/**
* Represents the closing delimiter that is used to separate chromosomes in
* the persistent representation.
*/
public final static String CHROM_DELIMITER_CLOSING = "]";
/*
*
* @author Klaus Meffert
* @since 3.0
*/
public Population(final Configuration a_config)
throws InvalidConfigurationException {
this(a_config, 100);
}
/*
* Constructs the Population from a list of Chromosomes. Does not use cloning!
*
* @param a_chromosomes the Chromosome's to be used for building the
* Population
*
* @author Klaus Meffert
* @since 2.0
*/
public Population(final Configuration a_config,
final IChromosome[] a_chromosomes)
throws InvalidConfigurationException {
this(a_config, a_chromosomes.length);
synchronized (m_chromosomes) {
for (int i = 0; i < a_chromosomes.length; i++) {
// In here we could test for null chromosomes, but this is skipped
// because of performance issues (although this may seem idiotic...)
m_chromosomes.add(a_chromosomes[i]);
}
}
setChanged(true);
}
/*
* Constructs the Population from a single Chromosome. Does not use cloning!
* @param a_chromosome the Chromosome to be used for building the Population
*
* @author Klaus Meffert
* @since 3.2
*/
public Population(final Configuration a_config,
final IChromosome a_chromosome)
throws InvalidConfigurationException {
this(a_config, 1);
if (a_chromosome == null) {
throw new IllegalArgumentException("Chromosome passed must not be null!");
}
synchronized (m_chromosomes) {
m_chromosomes.add(a_chromosome);
}
setChanged(true);
}
/*
* Constructs an empty Population with the given initial size.
* @param a_size the initial size of the empty Population. The initial size
* is not fix, it is just for optimized list creation.
*
* @author Klaus Meffert
* @since 2.0
*/
public Population(final Configuration a_config, final int a_size)
throws InvalidConfigurationException {
if (a_config == null) {
throw new InvalidConfigurationException("Configuration must not be null!");
}
m_config = a_config;
// Use a synchronized list (important for distributed computing!)
m_chromosomes = new Vector(a_size);
setChanged(true);
}
/*
* Constructs an empty Population with initial array size 100.
*
* @author Klaus Meffert
* @since 2.0
*/
public Population()
throws InvalidConfigurationException {
this(Genotype.getStaticConfiguration());
}
public Configuration getConfiguration() {
return m_config;
}
/**
* Adds a Chromosome to this Population. Does nothing when given null.
*
* @param a_toAdd the Chromosome to add
*
* @author Klaus Meffert
* @since 2.0
*/
public void addChromosome(final IChromosome a_toAdd) {
if (a_toAdd != null) {
synchronized (m_chromosomes) {
m_chromosomes.add(a_toAdd);
}
setChanged(true);
}
}
/**
* Adds all the Chromosomes in the given Population. Does nothing on null or
* an empty Population.
*
* @param a_population the Population to add
*
* @author Klaus Meffert
* @since 2.0
*/
public void addChromosomes(final Population a_population) {
if (a_population != null) {
synchronized (m_chromosomes) {
m_chromosomes.addAll(a_population.getChromosomes());
}
// The following would do the same:
// if (a_population.getChromosomes() != null) {
// int size = a_population.getChromosomes().size();
// for (int i = 0; i < size; i++) {
// IChromosome chrom = a_population.getChromosome(i);
// m_chromosomes.add(chrom);
// }
// }
setChanged(true);
}
}
/**
* Replaces all chromosomes in the population with the give list of
* chromosomes.
*
* @param a_chromosomes the chromosomes to make the population up from
*
* @author Klaus Meffert
*/
public void setChromosomes(final List a_chromosomes) {
synchronized (m_chromosomes) {
m_chromosomes = a_chromosomes;
}
setChanged(true);
}
/**
* Sets in the given Chromosome on the given index in the list of chromosomes.
* If the given index is exceeding the list by one, the chromosome is
* appended.
*
* @param a_index the index to set the Chromosome in
* @param a_chromosome the Chromosome to be set
*
* @author Klaus Meffert
* @since 2.0
*/
public void setChromosome(final int a_index, final IChromosome a_chromosome) {
if (m_chromosomes.size() == a_index) {
addChromosome(a_chromosome);
}
else {
synchronized (m_chromosomes) {
m_chromosomes.set(a_index, a_chromosome);
}
setChanged(true);
}
}
/**
* @return the list of Chromosome's in the Population. Don't modify the
* retrieved list by using clear(), remove(int) etc. If you do so, you need to
* call setChanged(true)
*
* @author Klaus Meffert
* @since 2.0
*/
public List<IChromosome> getChromosomes() {
return m_chromosomes;
}
/**
* @param a_index the index of the Chromosome to be returned
* @return Chromosome at given index in the Population
*
* @author Klaus Meffert
* @since 2.0
*/
public IChromosome getChromosome(final int a_index) {
return (IChromosome) m_chromosomes.get(a_index);
}
/**
* @return number of Chromosome's in the Population
*
* @author Klaus Meffert
* @since 2.0
*/
public int size() {
return m_chromosomes.size();
}
/**
* @return Iterator for the Chromosome list in the Population. Please be aware
* that using remove() forces you to call setChanged(true)
*
* @author Klaus Meffert
* @since 2.0
*/
public Iterator iterator() {
return m_chromosomes.iterator();
}
/**
* @return the Population converted into a list of Chromosome's
*
* @author Klaus Meffert, Dan Clark
* @since 2.0
*/
public IChromosome[] toChromosomes() {
return (IChromosome[]) m_chromosomes.toArray(
new IChromosome[m_chromosomes.size()]);
}
/**
* Determines the fittest Chromosome in the Population (the one with the
* highest fitness value) and memorizes it. This is an optimized version
* compared to calling determineFittesChromosomes(1).
*
* @return the fittest Chromosome of the Population
*
* @author Klaus Meffert
* @since 2.0
*/
public IChromosome determineFittestChromosome() {
if (!m_changed && m_fittestChromosome != null) {
return m_fittestChromosome;
}
Iterator it = m_chromosomes.iterator();
FitnessEvaluator evaluator = getConfiguration().getFitnessEvaluator();
double bestFitness;
if (evaluator.isFitter(2.0d, 1.0d)) {
bestFitness = -1.0d;
}
else {
bestFitness = Double.MAX_VALUE;
}
double fitness;
while (it.hasNext()) {
IChromosome chrom = (IChromosome) it.next();
fitness = chrom.getFitnessValue();
if (evaluator.isFitter(fitness, bestFitness)
|| m_fittestChromosome == null) {
m_fittestChromosome = chrom;
bestFitness = fitness;
}
}
setChanged(false);
return m_fittestChromosome;
}
/**
* Determines the fittest Chromosome in the population (the one with the
* highest fitness value) within the given indices and memorizes it. This is
* an optimized version compared to calling determineFittesChromosomes(1).
*
* @param a_startIndex index to begin the evaluation with
* @param a_endIndex index to end the evaluation with
* @return the fittest Chromosome of the population within the given indices
*
* @author Klaus Meffert
* @since 3.0
*/
public IChromosome determineFittestChromosome(int a_startIndex,
int a_endIndex) {
double bestFitness = -1.0d;
FitnessEvaluator evaluator = getConfiguration().getFitnessEvaluator();
double fitness;
int startIndex = Math.max(0, a_startIndex);
int endIndex = Math.min(m_chromosomes.size()-1, a_endIndex);
m_fittestChromosome = null;
for (int i = startIndex; i <= endIndex; i++) {
IChromosome chrom = (IChromosome) m_chromosomes.get(i);
fitness = chrom.getFitnessValue();
if (evaluator.isFitter(fitness, bestFitness)
|| m_fittestChromosome == null) {
m_fittestChromosome = chrom;
bestFitness = fitness;
}
}
return m_fittestChromosome;
}
/**
* Mark that for the population the fittest chromosome may have changed.
*
* @param a_changed true: population's fittest chromosome may have changed,
* false: fittest chromosome evaluated earlier is still valid
*
* @author Klaus Meffert
* @since 2.2
*/
protected void setChanged(final boolean a_changed) {
m_changed = a_changed;
setSorted(false);
}
/**
* @return true: population's chromosomes (maybe) were changed,
* false: not changed for sure
*
* @since 2.6
*/
public boolean isChanged() {
return m_changed;
}
/**
* Mark the population as sorted.
*
* @param a_sorted true: mark population as sorted
*
* @author Klaus Meffert
* @since 2.6
*/
protected void setSorted(final boolean a_sorted) {
m_sorted = a_sorted;
}
/**
* Determines whether the given chromosome is contained within the population.
* @param a_chromosome the chromosome to check
* @return true: chromosome contained within population
*
* @author Klaus Meffert
* @since 2.1
*/
public boolean contains(final IChromosome a_chromosome) {
return m_chromosomes.contains(a_chromosome);
}
/**
* Removes a chromosome in the list at the given index. Method has package
* visibility to signal that this is a method not to be used outside the
* JGAP kernel under normal circumstances.
*
* @param a_index index of chromosome to be removed in list
* @return removed Chromosome
*
* @author Klaus Meffert
* @since 2.4
*/
IChromosome removeChromosome(final int a_index) {
if (a_index < 0 || a_index >= size()) {
throw new IllegalArgumentException("Index must be within bounds!");
}
setChanged(true);
return (IChromosome) m_chromosomes.remove(a_index);
}
/**
* Cares that the population size does not exceed the maximum size given in
* the configuration.
*
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 3.2
*/
public void keepPopSizeConstant() throws InvalidConfigurationException {
int popSize = size();
// See request 1213752.
// ---------------------
int maxSize = getConfiguration().getPopulationSize();
// INaturalSelector selector = getConfiguration().getKeepPopConstantSelector();
// if (popSize > maxSize) {
// Population newPop = new Population(getConfiguration(), maxSize);
// selector.select(maxSize, this, newPop);
// m_chromosomes = newPop.getChromosomes();
// setChanged(true);
// }
IEvolutionMonitor monitor = getConfiguration().getMonitor();
boolean monitorActive = monitor != null;
while (popSize > maxSize) {
if (monitorActive) {
// Fire monitor with population and index of chromosome to be removed.
// -------------------------------------------------------------------
monitor.event(IEvolutionMonitor.MONITOR_EVENT_REMOVE_CHROMOSOME,
getConfiguration().getGenerationNr(),
new Object[] {this, new Integer(0)});
}
// Remove a chromosome.
// --------------------
/**@todo use dedicated selector for that*/
removeChromosome(0);
popSize--;
}
}
/**
* Sorts the Chromosome list and returns the fittest n Chromosomes in
* the population.
*
* @param a_numberOfChromosomes number of top performer chromosomes to be
* returned
* @return list of the fittest n Chromosomes of the population, or the fittest
* x Chromosomes with x = number of chromosomes in case n > x.
*
* @author Charles Kevin Hill
* @since 2.4
*/
public List determineFittestChromosomes(final int a_numberOfChromosomes) {
int numberOfChromosomes = Math.min(a_numberOfChromosomes,
getChromosomes().size());
if (numberOfChromosomes <= 0) {
return null;
}
if (!m_changed && m_sorted) {
return getChromosomes().subList(0, numberOfChromosomes);
}
// Sort the list of chromosomes using the fitness comparator
sortByFitness();
// Return the top n chromosomes
return getChromosomes().subList(0, numberOfChromosomes);
}
/**
* Sorts the chromosomes within the population according to their fitness
* value using ChromosomFitnessComparator. The fittest chromosome is then
* at index 0.
*
* @author Klaus Meffert
* @since 2.6
*/
public void sortByFitness() {
// The following construction could be cached but wrt that the
// evaluator registered with the configuration could change
// --> Don't cache it!
sort(new ChromosomeFitnessComparator(getConfiguration().
getFitnessEvaluator()));
setChanged(false);
setSorted(true);
m_fittestChromosome = (IChromosome) m_chromosomes.get(0);
}
/**
* Sorts the chromosomes within the population utilzing the given comparator.
*
* @param a_comparator the comparator to utilize for sorting
*
* @author Klaus Meffert
* @since 2.6
*/
protected void sort(Comparator a_comparator) {
Collections.sort(getChromosomes(), a_comparator);
}
/**
* Returns the genotype of the population, i.e. the list of genes in the
* Population.
*
* @param a_resolveCompositeGenes true: split encountered CompositeGenes
* into their single (atomic) genes
* @return genotype of the population
*
* @author Klaus Meffert
* @since 2.3
*/
public List getGenome(final boolean a_resolveCompositeGenes) {
List result = new Vector();
List chroms = getChromosomes();
int len = chroms.size();
for (int i = 0; i < len; i++) {
IChromosome chrom = (IChromosome) chroms.get(i);
Gene[] genes = chrom.getGenes();
int len2 = genes.length;
for (int j = 0; j < len2; j++) {
Gene gene = genes[j];
if (a_resolveCompositeGenes && gene instanceof ICompositeGene) {
addCompositeGene(result, (ICompositeGene) gene);
}
else {
addAtomicGene(result, gene);
}
}
}
return result;
}
/**
* Adds all the genes of a CompositeGene to a result list.<p>
* Note: Method calls itself recursively.
*
* @param a_result the list to add to
* @param a_gene the gene to start with
*
* @author Klaus Meffert
* @since 2.3
*/
private void addCompositeGene(final List a_result, final Gene a_gene) {
if (a_gene instanceof ICompositeGene) {
int len = a_gene.size();
for (int i = 0; i < len; i++) {
addCompositeGene(a_result, ( (ICompositeGene) a_gene).geneAt(i));
}
}
else {
addAtomicGene(a_result, a_gene);
}
}
/**
* Helper method for addCompositeGene.
*
* @param a_result List
* @param a_gene Gene
*
* @author Klaus Meffert
* @since 2.3
*/
private void addAtomicGene(final List a_result, final Gene a_gene) {
a_result.add(a_gene);
}
public boolean isSorted() {
return m_sorted;
}
/**
* The equals-method.
*
* @param a_pop the population instance to compare with
* @return true: given object equal to comparing one
*
* @author Klaus Meffert
* @since 2.6
*/
public boolean equals(Object a_pop) {
try {
return compareTo(a_pop) == 0;
} catch (ClassCastException e) {
// If the other object isn't an Population instance
// then we're not equal.
// ------------------------------------------------
return false;
}
}
/**
* This method is not producing symmetric results as -1 is more often returned
* than 1 (see description of return value).
*
* @param a_pop the other population to compare
* @return 1: a_pop is null or having fewer chromosomes or equal number
* of chromosomes but at least one not contained. 0: both populations
* containing exactly the same chromosomes. -1: this population contains fewer
* chromosomes than a_pop
*
* @author Klaus Meffert
* @since 2.6
*/
public int compareTo(Object a_pop) {
Population other = (Population) a_pop;
if (a_pop == null) {
return 1;
}
int size1 = size();
int size2 = other.size();
if (size1 != size2) {
if (size1 < size2) {
return -1;
}
else {
return 1;
}
}
List chroms2 = other.getChromosomes();
for (int i = 0; i < size1; i++) {
if (!chroms2.contains(m_chromosomes.get(i))) {
return 1;
}
}
return 0;
}
/**
* @return deeply cloned population instance
*
* @author Klaus Meffert
* @since 3.2
*/
public Object clone() {
try {
Population result = new Population(m_config);
// Precautiously set changed to true in case cloning is not 1:1
// ------------------------------------------------------------
result.m_changed = true;
result.m_sorted = false;
result.m_fittestChromosome = m_fittestChromosome;
int size = m_chromosomes.size();
for (int i = 0; i < size; i++) {
IChromosome chrom = (IChromosome) m_chromosomes.get(i);
result.addChromosome( (IChromosome) chrom.clone());
}
return result;
} catch (Exception ex) {
throw new CloneException(ex);
}
}
/**
* Clears the list of chromosomes. Normally, this should not be necessary.
* But especially in distributed computing, a fresh population has to be
* provided sometimes.
*
* @author Klaus Meffert
* @since 3.2
*/
public void clear() {
m_chromosomes.clear();
m_changed = true;
m_sorted = true;
m_fittestChromosome = null;
}
/**
* Returns a persistent representation of this chromosome, see interface Gene
* for description. Similar to CompositeGene's routine. But does not include
* all information of the chromosome (yet).
*
* @return string representation of this Chromosome's relevant parts of its
* current state
* @throws UnsupportedOperationException
*
* @author Klaus Meffert
* @since 3.2
*/
public String getPersistentRepresentation() {
StringBuffer b = new StringBuffer();
// Persist the chromosomes.
// ------------------------
IChromosome chrom;
for (int i = 0; i < m_chromosomes.size(); i++) {
chrom = (IChromosome) m_chromosomes.get(i);
if (! (chrom instanceof IPersistentRepresentation)) {
throw new RuntimeException("Population contains a chromosome of type "
+ chrom.getClass().getName()
+ " which does not implement"
+ " IPersistentRepresentation!");
}
b.append(CHROM_DELIMITER_HEADING);
b.append(StringKit.encode(chrom.getClass().getName()
+ CHROM_DELIMITER
+ ( (IPersistentRepresentation) chrom).
getPersistentRepresentation()));
b.append(CHROM_DELIMITER_CLOSING);
}
return b.toString();
}
/**
* Counterpart of getPersistentRepresentation.
*
* @param a_representation the string representation retrieved from a prior
* call to the getPersistentRepresentation() method
*
* @throws UnsupportedRepresentationException
*
* @author Klaus Meffert
* @since 3.2
*/
public void setValueFromPersistentRepresentation(String a_representation)
throws UnsupportedRepresentationException {
if (a_representation != null) {
try {
List r = split(a_representation);
String g;
m_chromosomes = new Vector();
// Obtain the chromosomes.
// -----------------------
Iterator iter = r.iterator();
StringTokenizer st;
String clas;
String representation;
IChromosome chrom;
while (iter.hasNext()) {
g = StringKit.decode( (String) iter.next());
st = new StringTokenizer(g, CHROM_DELIMITER);
if (st.countTokens() != 2)
throw new UnsupportedRepresentationException("In " + g + ", " +
"expecting two tokens, separated by " + CHROM_DELIMITER);
clas = st.nextToken();
representation = st.nextToken();
chrom = createChromosome(clas, representation);
m_chromosomes.add(chrom);
}
setChanged(true);
} catch (Exception ex) {
throw new UnsupportedRepresentationException(ex.toString());
}
}
}
/**
* Creates a new Chromosome instance.<p>
* Taken and adapted from CompositeGene.
*
* @param a_chromClassName name of the Chromosome class
* @param a_persistentRepresentation persistent representation of the
* Chromosome to create (could be obtained via getPersistentRepresentation)
*
* @return newly created Chromosome
* @throws Exception
*
* @author Klaus Meffert
* @since 3.2
*/
protected IChromosome createChromosome(String a_chromClassName,
String a_persistentRepresentation)
throws Exception {
Class chromClass = Class.forName(a_chromClassName);
Constructor constr = chromClass.getConstructor(new Class[] {Configuration.class});
IChromosome chrom = (IChromosome) constr.newInstance(new Object[] {
getConfiguration()});
( (IPersistentRepresentation) chrom).setValueFromPersistentRepresentation(
a_persistentRepresentation);
return chrom;
}
/**
* Splits the input a_string into individual chromosome representations.<p>
* Taken and adapted from CompositeGene.
*
* @param a_string the string to split
* @return the elements of the returned array are the persistent
* representation strings of the population's components
* @throws UnsupportedRepresentationException
*
* @author Klaus Meffert
* @since 3.2
*/
protected static final List split(String a_string)
throws UnsupportedRepresentationException {
List a = Collections.synchronizedList(new ArrayList());
// No Header data.
// ---------------
// Chromosome data.
// ----------------
StringTokenizer st = new StringTokenizer
(a_string, CHROM_DELIMITER_HEADING + CHROM_DELIMITER_CLOSING, true);
while (st.hasMoreTokens()) {
if (!st.nextToken().equals(CHROM_DELIMITER_HEADING)) {
throw new UnsupportedRepresentationException(a_string + " no open tag");
}
String n = st.nextToken();
if (n.equals(CHROM_DELIMITER_CLOSING)) {
a.add(""); /* Empty token */
}
else {
a.add(n);
if (!st.nextToken().equals(CHROM_DELIMITER_CLOSING)) {
throw new UnsupportedRepresentationException
(a_string + " no close tag");
}
}
}
return a;
}
/***
* 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)
*
* @return the computed hashcode
*
* @author Klaus Meffert
* @since 3.5
*/
public int hashCode() {
int i, size = size();
IChromosome s;
int twopower = 1;
// For empty population we want a special value different from other
// hashcode implementations.
// ------------------------------------------------------------------
int localHashCode = -593;
for (i = 0; i < size; i++, twopower = 2 * twopower) {
s = getChromosome(i);
localHashCode = 31 * localHashCode + s.hashCode();
}
return localHashCode;
}
}