/*
* 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.*;
/**
* Takes away the fitness offset of the population to evolve.
* The fitness function values of the population of {@link org.jgap.IChromosome}
* instances will start from a minimum of 1 afterwards.
* </p>
* <p>
* The removal of an offset in the fitness values of a population strengthens
* the "survival of the fittest" effect of a selector that performs selection
* upon fitness values. A high offset in the fitness values of a population
* lowers the relative difference between the fitness values of the Chromosomes
* in a population.
* </p>
* <h3>Example of applicability</h3>
* <p>
* You are optimizing a black box with <i>n</i> parameters that are mapped
* to {@link org.jgap.IChromosome} instances each having <i>n</i>
* {@link org.jgap.Gene} instances.<br>
* You want to minimize the answer time of the black box and provide
* a {@link org.jgap.FitnessFunction#evaluate(org.jgap.IChromosome)}
* that takes the genes out of the chromosome, put's it's
* {@link org.jgap.Gene#getAllele()}
* values to the parameters and measures the answer time of the black box
* (by invoking it's service to optimize). <br>
* The longer the time takes, the worse it's fitness is, so you have to
* invert the measured times to fitness values:
* <a name="bboptimizer"/>
* <pre>
* <font color="#0011EE">
* class BlackBoxOptimizer extends org.jgap.FitnessFunction{
* private BlackBox bbox;
* <font color="#999999">//Additional code: constructors</font>
* <font color="#999999">...</font>
* public double evaluate(org.jgap.IChromosome chromosome){
* double fitness = 0;
* <font color="#999999">// get the Gene[] & put the parameters into the box.
* ...
* </font>
* long duration = System.currentTimeMillis();
* <font color="#999999">
* // You certainly will use an advanced StopWatch...</font>
* this.bbox.service();
* <font color="#999999">// The black boxes service to optimize.</font>
* duration = System.currentTimeMillis()-duration;
* <font color="#999999">// transform the time into fitness value:</font>
* fitness = double.MAX_VALUE - (double)duration;
* return fitness;
* }
* }
* </font>
* </pre>
* </p>
* <p>
* <h4>We might get the following results (each row stands for a Chromosome,
* the table is a population):</h4>
* <table border="1">
* <tr align="left" valign="top">
* <th>
* duration
* </th>
* <th>
* fitness
* </th>
* <th>
* piece of fitness cake
* </th>
* </tr>
* <tr align="left" valign="top">
* <td>
* 2000
* </td>
* <td>
* 9218868437227403311
* </td>
* <td>
* 33.333333333333336949106088992532 %
* </td>
* </tr>
* <tr align="left" valign="top">
* <td>
* 3000
* </td>
* <td>
* 9218868437227402311
* </td>
* <td>
* 33.333333333333333333333333333333 %
* </td>
* </tr>
* <tr align="left" valign="top">
* <td>
* 4000
* </td>
* <td>
* 9218868437227401311
* </td>
* <td>
* 33.333333333333329717560577674135 %
* </td>
* </tr>
* </table>
* </p>
* <p>
* If any {@link org.jgap.NaturalSelector} performs selection based upon the
* fitness values, it will have to put those values in relation to each other.
* As a fact, the probability to select the Chromosome that contained the black
* box parameters that caused an answer time of 4000 ms is "equal" to the
* probability to select the Chromosome that caused a black box answer time to
* be 2000 ms!
* </p>
* <p>
* Of course one could work around that problem by replacing the
* <tt>Integer.MAX_VALUE</tt> transformation by a fixed maximum value the black
* box would need for the service.
* But what, if you have no guaranteed maximum answer time for the service of
* the black box ?
* Even if you have got one, it will be chosen sufficently high above the
* average answer time thus letting your fitness function return values with a
* high offset in the fitness.
* </p>
* <p>
* <h4>This is, what happens, if you use this instance for fitness
* evaluation:</h4>
* <table border="1">
* <tr align="left" valign="top">
* <th>
* duration
* </th>
* <th>
* fitness
* </th>
* <th>
* piece of fitness cake
* </th>
* </tr>
* <tr align="left" valign="top">
* <td>
* 2000
* </td>
* <td>
* 2001
* </td>
* <td>
* 66.63 %
* </td>
* </tr>
* <tr align="left" valign="top">
* <td>
* 3000
* </td>
* <td>
* 1001
* </td>
* <td>
* 33.33 %
* </td>
* </tr>
* <tr align="left" valign="top">
* <td>
* 4000
* </td>
* <td>
* 1
* </td>
* <td>
* 0.03 %
* </td>
* </tr>
* </table>
* </p>
* <h3>Example of usage</h3>
*
* <p>
* This example shows how to use this instance for cutting fitness offsets.
* It is the same example as used <a href="#bboptimizer">above</a>.
* <pre>
* <font color="#0011EE">
* class BlackBoxOptimizer extends org.jgap.FitnessFunction{
* <font color="#999999">// Additional code: constructors
* ...</font>
* public double evaluate(org.jgap.IChromosome chromosome){
* <font color="#999999">.... // As shown above.</font>
* }
*
* public void startOptimization(org.jgap.Configuration gaConf)
* throws InvalidConfigurationException{
* <font color="#999999">
* // The given Configuration may be preconfigured with
* // NaturalSelector & GeneticOperator instances,.
* // But should not contain a FitnessFunction or BulkFitnessFunction!
* </font>
* <b>gaConf.setBulkFitnessFunction(new BulkFitnessOffsetRemover(this));</b>
* <font color="#999999">// Why does it work? We implement FitnessFunction!
* // Still to do here:
* // - Create a sample chromosome according to your blackbox & set it to
* // the configuration.
* // - Create a random inital Genotype.
* // - loop over a desired amount of generations invoking
* // aGenotype.evolve()..</font>
* }
* }
* </font>
* </pre>
* </p>
*
* @author Achim Westermann
* @since 2.2
*
*/
public class BulkFitnessOffsetRemover
extends BulkFitnessFunction {
/** String containing the CVS revision. Read out via reflection!*/
private final static String CVS_REVISION = "$Revision: 1.12 $";
/*
* Replace this member by the Configuration as soon as Configuration allows
* bulk fitness function and fitness function to be stored both in it.
*/
private FitnessFunction m_ff;
/**@todo This constructor is planned but not possible yet,
* as the Configuration permits bulk fitness function and
* simple fitness function both existing in it at the same time.
*/
/*
public BulkFitnessOffsetRemover(Configuration conf){
if(m_activeConfiguration)
}
*/
/**
* <p>
* The last generations offset.
* This has to be stored because Chromosomes that were put into the new
* generation's candidate list already have the fitness value without offset
* from their previous evaluation.
* </p>
* <P>
* We try to avoid evaluations of the fitness function
* as it might be expensive, so we reuse fitness values.
* If a Chromosome already has a fitness value >0 this
* previousOffset is added to it's fitness to allow
* comparing this Chromosome's fitness with newly
* evaluated Chromosomes (which still have the offset from the evaluation).
* </p>
*/
private double m_previousOffset;
public BulkFitnessOffsetRemover(final FitnessFunction a_ff) {
if (a_ff == null) {
throw new IllegalArgumentException("Fitness function must not be null!");
}
m_ff = a_ff;
}
/* (non-Javadoc)
* @see org.jgap.BulkFitnessFunction#evaluate(org.jgap.Chromosome[])
*/
public void evaluate(final Population a_chromosomes) {
double offset = Double.MAX_VALUE;
double curFitness;
Iterator itChromosomes = a_chromosomes.iterator();
IChromosome chromosome;
while (itChromosomes.hasNext()) {
chromosome = (IChromosome) itChromosomes.next();
/*
* This is a workaround:
* We have to check, wethter a Chromosome has
* already been evaluated, because it may be too
* expensive to unconditionally evaluate each Chromosome.
* We use the "caching of fitness values" of the Chromosome.
* But in the current Configuration,
* there is no fittnessFunction: We have a bulk fitness function
* assigned.
* Look at the code of Chromosome.getFitnessValue():
* In that case, a negative value will be returned.
* If a redesign of that method is made, this has to be changed
* here too.. .
*/
curFitness = chromosome.getFitnessValueDirectly();
if (curFitness < 0) {
// OK, get it from our fitness function.
curFitness = m_ff.getFitnessValue(chromosome);
// And store it to avoid evaluation of the same Chromosome again:
chromosome.setFitnessValue(curFitness);
}
else {
/*
* Those have fitness values already without offset
* of the previous evaluation.
* Reattach it to allow comparison.
* Else these Chromosomes would be the unfittest and
* additionally disallow cutting a huge offset from the others.
*/
curFitness += m_previousOffset;
chromosome.setFitnessValue(curFitness);
}
// search for the offset that is to be cut:
offset = (offset < curFitness) ? offset : curFitness;
}
/*
* Now we have the classical evaluated Chromosomes
* and the minimum fitness.
* It would be easy to simply subtract that value from
* each Chromosomes fitness. But in that case the
* unfittest Chromosome would possible have no chance to survive.
* In a WeightedRouletteSelector it would not get
* a single slot to be selected as it's fitness value
* would be zero.
* So we have to leave at least a fitness value of 1.
* Ups, if forgot: Neil throws exceptions, whenever a
* fitness value is below 1 and also does not accept
* assignment of fitness values <1. Ok, so he ensures
* that every Chromosome may survive...
*/
offset--;
m_previousOffset = offset;
// offset cannot be <0... thx to fitness value policy of jgap.
// finally remove the offset from every fitness value:
itChromosomes = a_chromosomes.iterator();
while (itChromosomes.hasNext()) {
chromosome = (IChromosome) itChromosomes.next();
chromosome.setFitnessValue(chromosome.getFitnessValue() - offset);
}
}
/**
* <p>
* Using this instance to remove the fitness offset in the
* populations brings the advantage of getting a selection
* more sensitive to the differences of fitness of the chromosomes.
* </p>
* <p>
* The disadvantage is, that the fitness values are modified.
* The modification is good for jgap's selection method but
* bad for the guys that want to see the success of your
* work, or need a proof that a GA improves over time:
* <br>
* The value of {@link org.jgap.Genotype#getFittestChromosome()}
* does not seem to increase over the generations. Most often
* it becomes worse. This is caused by the fact, that all
* Chromosomes are getting better over time
* (the fitness interval of all Chromosomes gets narrower) and
* the offset that may be cut becomes bigger.
* </p>
* <p>
* If you want to get an absolute value independant from the
* offset that is cut off from the chromosome's fitness value,
* this method has to be used.
* </p>
* <p>
* Stop reading here because a
* </p>
* <h4>Mathematical Proof</h4>
* <p>
* is following.
* How can it work to get the absolute value for all
* Chromosomes fitness values? Some Chromosomes may have lived
* for many generations and everytime their fitness was
* evaluated here, the old offset was added and a new one was
* calculated and subtracted from the fitness value.
* </p>
* <p>
* Each bulk fitness evaluation a Chromosome experiences,
* it's fitness value <i>F</i> get's an addition of the old offset
* <i>O<sub>(n-1)</sub></i>
* and a substraction by the new offset <i>O<sub>n</sub></i>.<br>
* <i><sub>n</sub></i> is the generation index.
*
* <pre>
* F<sub>1</sub> = F<sub>0</sub> + O<sub>0</sub> - O<sub>1</sub>
* F<sub>2</sub> = F<sub>1</sub> + O<sub>1</sub> - O<sub>2</sub>
* F<sub>3</sub> = F<sub>2</sub> + O<sub>2</sub> - O<sub>3</sub>
*
* =>
*
* 1) F<sub>n</sub> = <b>F<sub>(n-1)</sub></b>
* + O<sub>(n-1)</sub> - O<sub>n</sub>
*
* 2) <b>F<sub>(n-1)</sub></b> = F<sub>(n-2)</sub>
* + O<sub>(n-2)</sub> - O<sub>(n-1)</sub>
*
* 2 in 1)
* F<sub>n</sub> = (F<sub>(n-2)</sub> + O<sub>(n-2)</sub>
* - O<sub>(n-1)</sub>) + O<sub>(n-1)</sub> - O<sub>n</sub>
* F<sub>n</sub> = F<sub>(n-2)</sub> + O<sub>(n-2)</sub> - O<sub>n</sub>
*
* We made a step over 2 generations: With the current offset and the
* fitness & offset of the
* "preprevious" generation we can calculate the current fitness.
* We can assume that this generation stepping works for farer steps
* <sub>m</sub> (just continue step 2) until you have a generation step value
* high enough ;-))
*
* => F<sub>n</sub> = F<sub>(n-m)</sub> + O<sub>(n-m)</sub> - O<sub>n</sub>
*
* We want to get the original absolute value of fitness:
*
* 3) m := n
*
* => F<sub>n</sub> = F<sub>0</sub> + O<sub>0</sub> - O<sub>n</sub>
*
* solved to F<sub>0</sub> our original value:
*
* F<sub>0</sub> = F<sub>n</sub> + O<sub>n</sub> - O<sub>0</sub>
*
* And our initial offset {@link #m_previousOffset O<sub>0</sub>} is zero!
* </pre>
* </p>
* <p>
* This shows, that it is possible to compute the original fitness value of a
* Chromosome from it's current fitness value and the
* {@link #m_previousOffset previous offset}
* regardless of the amounts of generations between original evaluation and
* the current generation.
* </p>
* @param a_individuum any Chromosome that is normally being evaluated by
* this <tt>BulkFitnessFunction</tt>
* @return the original fitness value as returned by the registered
* {@link #m_ff fitnessFunction} instance.
*/
public double getAbsoluteFitness(final IChromosome a_individuum) {
double fitness = a_individuum.getFitnessValue();
if (fitness < 0.0) {
// OK, get it from our fitness function.
fitness = m_ff.getFitnessValue(a_individuum);
// And store it to avoid evaluation of the same Chromosome again:
a_individuum.setFitnessValue(fitness);
}
else {
/*
* Those have fitness values already without offset
* of the previous evaluation.
* Reattach it to allow comparison.
* Else these Chromosomes would be the unfittest and
* additionally disallow cutting a huge offset from the others.
*/
fitness += m_previousOffset;
}
return fitness;
}
/**
* @return deep clone of current instance
*
* @author Klaus Meffert
* @since 3.2
*/
public Object clone() {
FitnessFunction ff = (FitnessFunction)m_ff.clone();
BulkFitnessOffsetRemover result = new BulkFitnessOffsetRemover(ff);
return result;
}
}