/*
* 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 examples.gp.paintedDesert;
import java.io.*;
import java.util.*;
import org.jgap.*;
import org.jgap.event.*;
import org.jgap.gp.*;
import org.jgap.gp.function.*;
import org.jgap.gp.impl.*;
import org.jgap.util.*;
/**
* The Painted Desert problem from Koza's "Evolution of Emergent Cooperative Behavior
* using Genetic Programming". The problem is to create the same genetic program for
* a group of ants that will move three colors of sand into columns of like sand.
*
* @author Scott Mueller
* @since 3.2
*/
public class PaintedDesertProblem
extends GPProblem {
/** String containing the CVS revision. Read out via reflection!*/
private final static String CVS_REVISION = "$Revision: 1.4 $";
/**
* Local copy of the map read into from the file.
*/
private int[][] m_map;
/**
* Set by readMap and passed to the AntMap constructor
*/
private Ant[] m_ants;
/**
* Holds the AntMap object used to hold the initial and current map of sand
* and Ants.
*/
private static AntMap m_antMap;
/**
* The maximum number of locations in the East or X direction.
*/
private static int m_maxx;
/**
* The maximum number of locaitons in the North or Y direction.
*/
private static int m_maxy;
/**
* The number of ants in the map.
*/
private static int m_popSize;
/**
* Maximum number of moves allowed.
*/
private static int m_maxMoves = 300;
/**
* Creates the Painted Desert Problem using the GPConfiguration.
* @param a_conf
* @throws InvalidConfigurationException
*/
public PaintedDesertProblem(GPConfiguration a_conf)
throws InvalidConfigurationException {
super(a_conf);
}
/**
* Sets up the functions to use and other parameters. Then creates the
* initial genotype.
*
* @return the genotype created
* @throws InvalidConfigurationException
*
* @author Scott Mueller
*/
public GPGenotype create()
throws InvalidConfigurationException {
Class[] types = {CommandGene.VoidClass};
Class[][] argTypes = { {}
};
int[] minDepths = new int[] {2};
int[] maxDepths = new int[] {8};
GPConfiguration conf = getGPConfiguration();
CommandGene[][] nodeSets = { {
new SubProgram(conf, new Class[] {CommandGene.VoidClass,
CommandGene.VoidClass, CommandGene.VoidClass}),
new SubProgram(conf, new Class[] {CommandGene.VoidClass,
CommandGene.VoidClass}),
new X(conf),
new Y(conf),
new Carrying(conf),
new SandColor(conf),
new GO_N(conf),
new GO_E(conf),
new GO_S(conf),
new GO_W(conf),
new MoveRandom(conf),
new Pickup(conf),
new IfDrop(conf, CommandGene.IntegerClass),
new IfLessThanOrEqual(conf, CommandGene.IntegerClass),
new IfLessThanZero(conf, CommandGene.IntegerClass),
new IfDrop(conf, CommandGene.IntegerClass),
}
};
// Create genotype with initial population.
return GPGenotype.randomInitialGenotype(conf, types, argTypes, nodeSets,
minDepths, maxDepths, 5000, new boolean[] {!true}, true);
}
/**
* Reads the map from a file. The first line contains the number of X and Y locations and
* the number of Ants on the map. A 'b' at a location represents a Black grain of sand.
* A 's' represents a striped grain of sands. A 'g' represents a grey grain of sand. An
* 'A' represents an Ant. A 'B' represents an Ant and a black grain of sand at the
* same location. A 'S' represents an Ant and a striped grain of sand at the same location.
* A 'G' represents an Ant and a grey grain of sand at the same location.
*
* A side effect of this function is setting the m_ants member variable.
*
* @param a_filename The location of the file containing the map information.
* @return An array of the sand locations read in from the map.
* @throws Exception
*/
private int[][] readMap(String a_filename)
throws Exception {
LineNumberReader lnr;
try {
lnr = new LineNumberReader(new FileReader(a_filename));
} catch (FileNotFoundException fex) {
throw new FileNotFoundException("File not found: " +
new File(".").getAbsolutePath() +
a_filename);
}
// Read dimensions of trail and the number of ants.
try {
StringTokenizer st = new StringTokenizer(lnr.readLine());
m_maxx = Integer.parseInt(st.nextToken());
m_maxy = Integer.parseInt(st.nextToken());
m_popSize = Integer.parseInt(st.nextToken());
int[][] result = new int[m_maxx][m_maxy];
m_ants = new Ant[m_popSize];
int y;
int antIndex = 0;
for (y = 0; y < m_maxy; y++) {
String s = lnr.readLine();
System.out.println(s);
if (s == null) {
throw new RuntimeException("Ant map file ended prematurely");
}
int x;
for (x = 0; x < s.length() & x < m_maxx; x++) {
if (s.charAt(x) == ' ') {
result[x][y] = AntMap.EMPTY;
}
else if (s.charAt(x) == 'b') {
result[x][y] = AntMap.BLACK;
}
else if (s.charAt(x) == 'g') {
result[x][y] = AntMap.GRAY;
}
else if (s.charAt(x) == 's') {
result[x][y] = AntMap.STRIPED;
}
else if (s.charAt(x) == 'A') {
result[x][y] = AntMap.EMPTY;
m_ants[antIndex] = new Ant(x, y);
antIndex++;
}
else if (s.charAt(x) == 'S') {
result[x][y] = AntMap.STRIPED;
m_ants[antIndex] = new Ant(x, y);
antIndex++;
}
else if (s.charAt(x) == 'G') {
result[x][y] = AntMap.GRAY;
m_ants[antIndex] = new Ant(x, y);
antIndex++;
}
else if (s.charAt(x) == 'B') {
result[x][y] = AntMap.BLACK;
m_ants[antIndex] = new Ant(x, y);
antIndex++;
}
else {
throw new RuntimeException("Bad character '" + s.charAt(x) +
"' on line number " + lnr.getLineNumber() +
" of the Ant map file.");
}
}
// fill out rest of X's
for (int z = x; z < m_maxx; z++) {
result[z][y] = AntMap.EMPTY;
}
}
// fill out rest of Y's
for (int z = y; z < m_maxy; z++) {
for (int x = 0; x < m_maxx; x++) {
result[x][z] = AntMap.EMPTY;
}
}
return result;
} catch (NumberFormatException e) {
throw new RuntimeException(
"The Ant map file does not begin with x and y and nbrOfAnts integer values.");
} catch (IOException e) {
throw new RuntimeException(
"The Ant map file could not be read due to an IOException:\n" + e);
}
}
/**
* Runs the Painted Desert Problem
*
* @param args The location of the ant map file is optional
* @throws Exception
*
* @author Scott Mueller
*/
public static void main(String[] args) {
try {
System.out.println("Painted Desert Problem");
GPConfiguration config = new GPConfiguration();
config.setSelectionMethod(new TournamentSelector(3));
config.setGPFitnessEvaluator(new DeltaGPFitnessEvaluator());
int popSize = 300;
String filename;
if (args.length == 1) {
filename = args[0];
}
else {
filename = "standard.desert";
}
System.out.println("Using population size of " + popSize);
System.out.println("Using map " + filename);
config.setMaxInitDepth(10);
config.setPopulationSize(popSize);
final PaintedDesertProblem problem = new PaintedDesertProblem(config);
GPFitnessFunction func = problem.createFitFunc();
config.setFitnessFunction(func);
config.setCrossoverProb(0.4f);
config.setReproductionProb(0.6f);
config.setFunctionProb(0.6f);
config.setNewChromsPercent(0.3f);
config.setStrictProgramCreation(true);
config.setUseProgramCache(false);
GPGenotype gp = problem.create();
gp.setVerboseOutput(true);
// Read the map from file.
problem.m_map = problem.readMap(filename);
problem.displaySolution(problem.m_map, problem.m_map);
m_antMap = new AntMap(problem.m_map, problem.m_ants);
// Simple implementation of running evolution in a thread.
final Thread t = new Thread(gp);
IEventManager eventManager = config.getEventManager();
eventManager.addEventListener(GeneticEvent.
GPGENOTYPE_EVOLVED_EVENT,
new GeneticEventListener() {
public void geneticEventFired(GeneticEvent a_firedEvent) {
GPGenotype genotype = (GPGenotype) a_firedEvent.getSource();
int evno = genotype.getGPConfiguration().getGenerationNr();
double freeMem = SystemKit.getFreeMemoryMB();
if (evno % 10 == 0) {
double bestFitness = genotype.getFittestProgram().
getFitnessValue();
System.out.println("Evolving generation " + evno
+ ", best fitness: " + bestFitness
+ ", memory free: " + freeMem + " MB");
}
if (evno > 500000) {
t.stop();
}
else {
try {
// Collect garbage if memory low.
if (freeMem < 50) {
System.gc();
t.sleep(500);
}
else {
// Avoid 100% CPU load.
t.sleep(30);
}
} catch (InterruptedException iex) {
iex.printStackTrace();
System.exit(1);
}
}
}
});
eventManager.addEventListener(GeneticEvent.
GPGENOTYPE_NEW_BEST_SOLUTION,
new GeneticEventListener() {
public void geneticEventFired(GeneticEvent a_firedEvent) {
GPGenotype genotype = (GPGenotype) a_firedEvent.getSource();
int evno = genotype.getGPConfiguration().getGenerationNr();
String indexString = "" + evno;
while (indexString.length() < 5) {
indexString = "0" + indexString;
}
// String filename = "painteddesert_best" + indexString + ".png";
IGPProgram best = genotype.getAllTimeBest();
// Display solution's final map.
// -----------------------------
AntMap antmap = (AntMap) best.getApplicationData();
problem.displaySolution(antmap.getMap(), antmap.getInitialMap());
java.text.DateFormat df = java.text.DateFormat.getTimeInstance(java.
text.DateFormat.SHORT);
String time = df.format(new java.util.Date());
System.out.println(time + " Number of moves: " + antmap.getMoveCount());
double bestFitness = genotype.getFittestProgram().
getFitnessValue();
if (bestFitness < 0.001) {
genotype.outputSolution(best);
t.stop();
System.exit(0);
}
}
});
//
// eventManager.addEventListener(GeneticEvent.
// GPGENOTYPE_NEW_BEST_SOLUTION,
// new MyGeneticEventListener(problem, t));
t.start();
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
/**
* Display ant map as found by GP.
*
* @param a_antmap the map containing the ants and grains of sand
*/
private static void displaySolution(int[][] a_antmap, int[][] origMap) {
for (int y = 0; y < m_maxy; y++) {
for (int x = 0; x < m_maxx; x++) {
char toPrint = '?';
int c = a_antmap[x][y];
switch (c) {
case AntMap.ANT_AT_POSITION:
toPrint = 'A';
break;
case AntMap.BLACK:
toPrint = 'b';
break;
case AntMap.GRAY:
toPrint = 'g';
break;
case AntMap.EMPTY:
toPrint = ' ';
break;
case AntMap.STRIPED:
toPrint = 's';
break;
}
System.out.print(toPrint);
}
System.out.print(" ");
for (int x = 0; x < m_maxx; x++) {
char toPrint = '?';
int c = origMap[x][y];
switch (c) {
case AntMap.ANT_AT_POSITION:
toPrint = 'A';
break;
case AntMap.BLACK:
toPrint = 'b';
break;
case AntMap.GRAY:
toPrint = 'g';
break;
case AntMap.EMPTY:
toPrint = ' ';
break;
case AntMap.STRIPED:
toPrint = 's';
break;
}
System.out.print(toPrint);
}
System.out.println();
}
System.out.println();
}
private GPFitnessFunction createFitFunc() {
return new AntFitnessFunction();
}
/**
* Represents the fitness funtion. Counts the grains of sand to that are not in the
* correct column. This varies from the measurement used by Koza and tries to penalize
* the ants that are further from the proper location.
*
* @author Scott Mueller
*
*/
class AntFitnessFunction
extends GPFitnessFunction {
private static final int VALUE1 = 100;
protected double evaluate(final IGPProgram a_subject) {
return computeRawFitness(a_subject);
}
public double computeRawFitness(final IGPProgram a_program) {
double error = 0.0f;
Object[] noargs = new Object[0];
// Initialize local stores.
a_program.getGPConfiguration().clearStack();
a_program.getGPConfiguration().clearMemory();
a_program.setApplicationData(m_antMap);
try {
m_antMap.init();
// Execute the program for each ant in turn.
for (int antIndex = 0; antIndex < m_popSize; antIndex++) {
m_antMap.nextAnt();
a_program.execute_void(0, noargs);
}
// Determine success of individual.
// --------------------------------
error = (double) m_antMap.fitness();
} catch (IllegalStateException iex) {
error = GPFitnessFunction.MAX_FITNESS_VALUE;
}
return error;
}
}
/**
* Resets the ants to initial positions. So they can be run with the next version
* of the program.
*/
public void resetAnts() {
for (int antIndex = 0; antIndex < m_popSize; antIndex++) {
m_antMap.getAnts()[antIndex].reset();
}
}
}