Tuesday, May 27, 2008

Coding Haldane's Dilemma

// Here's the code; I've been lazy, so you have to change constants and
// recompile it to alter incremental fitness (INC)
//
// This code is public domain. Enjoy.

import java.util.Random;
import java.util.Arrays;
import java.util.Comparator;

class Substitute
{
/** How many critters to have **/
private static final int CRITTERS = 1024;
/** How many children per critter **/
private static final int CHILDREN = 2;
/** How many selectable traits to have **/
private static final int TRAITS = 20;
/** How many copies of gene in the DNA **/
private static final int COPIES = 2;
// Note that you can't just change COPIES and expect
// everything to work!
/** Rate of increase, must be less than CHILDREN **/
private static final double INC = 1.1;
/** How may trials to run **/
private static final int TRIALS = 100;

/** A random number generator for initial state **/
private Random mRand = new Random(System.currentTimeMillis());

/** How many generations have gone by **/
private int mGenerations = 0;

class Critter
{
/** No-args constructor **/
Critter()
{
for (int i = 0; i < TRAITS; i++)
{
mDNA[0][i] = mDNA[1][i] = false;
}
}
/** Constructor from two parents **/
Critter(Critter aP1, Critter aP2)
{
for (int i = 0; i < TRAITS; i++)
{
// Choose one chromosome randomly from each parent.
int c1 = mRand.nextInt(COPIES);
int c2 = mRand.nextInt(COPIES);
mDNA[0][i] = aP1.mDNA[c1][i];
mDNA[1][i] = aP2.mDNA[c2][i];
}
}

/** Give yourself a beneficial mutation **/
void setDNA(int aPos)
{
mDNA[0][aPos] = true;
}

/** Do you have this mutation? **/
boolean getDNA(int aPos)
{
return (mDNA[0][aPos] || mDNA[1][aPos]);
}

/** DNA of critter **/
private final boolean mDNA[][] =
new boolean[COPIES][TRAITS];
};

/** Sort version that has mutations strictly ordered **/
public static Comparator LOWEST_FIRST = new Comparator()
{
/** Compare two Critters **/
public int compare(Object aOne, Object aTwo)
{
Critter l1 = (Critter)aOne;
Critter l2 = (Critter)aTwo;
for (int i = 0; i < TRAITS; i++)
{
boolean mine = l1.mDNA[0][i] | l1.mDNA[1][i];
boolean theirs = l2.mDNA[0][i] | l2.mDNA[1][i];
if (mine != theirs)
{
// Smaller is better
return mine ? -1 : 1;
}
}
return 0;
}
/** Not using this, don't care **/
public boolean equals(Object aComp) { return false; }
};

/** Sort version that has mutations equivalent **/
public static Comparator ALL_SAME = new Comparator()
{
/** Compare two Critters **/
public int compare(Object aOne, Object aTwo)
{
Critter l1 = (Critter)aOne;
Critter l2 = (Critter)aTwo;
int total = 0;
for (int i = 0; i < TRAITS; i++)
{
boolean mine = l1.mDNA[0][i] | l1.mDNA[1][i];
boolean theirs = l2.mDNA[0][i] | l2.mDNA[1][i];
if (mine != theirs)
{
// Smaller is better
total += mine ? -1 : 1;
}
}
return total;
}
/** Not using this, don't care **/
public boolean equals(Object aComp) { return false; }
};

/** DNA of current generation **/
private final Critter mCurrent[] = new Critter[CRITTERS];
/** DNA of new generation **/
private final Critter mNext[] =
new Critter[CRITTERS * CHILDREN];

public Substitute()
{
// Create the critters
for (int i = 0; i < CRITTERS; i++)
{
mCurrent[i] = new Critter();
}
// One beneficial mutation in each trait to start;
// each one in a different critter.
for (int i = 0; i < TRAITS; i++)
{
mCurrent[i].setDNA(i);
}
}

public int getGenerations() { return mGenerations; }

public void nextGeneration()
{
int remains = CRITTERS;
int lWhere = 0;
for (int i = 0; i < CRITTERS / 2; i++)
{
// Choose two random current critters
Critter p1 = getRandom(mCurrent, remains--);
Critter p2 = getRandom(mCurrent, remains--);
// Reproduce them.
for (int j = 0; j < CHILDREN * 2; j++)
{
mNext[lWhere++] = new Critter(p1, p2);
}
}
// Kill off random children to simulate how
// difficult life is for everyone.
remains = CRITTERS * CHILDREN;
long target = Math.round(INC * CRITTERS);
while (remains > target)
{
getRandom(mNext, remains--);
}
// Pick out the genetically fittest.
// Change LOWEST_FIRST to ALL_SAME to give equal
// weight to all mutations
Arrays.sort(mNext, 0, remains, LOWEST_FIRST);
// And replace the current generation with the best
for (int i = 0; i < CRITTERS; i++)
{
mCurrent[i] = mNext[i];
}
mGenerations++;
}

/** Print stats and see if we should stop yet */
public boolean analyze()
{
boolean bStop = true;
for (int i = 0; i < TRAITS; i++)
{
int tc = 0;
for (int j = 0; j < CRITTERS; j++)
{
if (mCurrent[j].getDNA(i))
{
tc++;
}
}
if (tc < CRITTERS)
{
bStop = false;
}
if (tc == 0)
{
// Lost a mutation! Replacing...
int pos = mRand.nextInt(CRITTERS);
mCurrent[pos].setDNA(i);
}
}
return bStop;
}

/**
* Pick a random critter from the array, and
* shrink the array down so it's removed.
*/
private Critter getRandom(Critter[] aGroup, int aCount)
{
int c1 = mRand.nextInt(aCount);
Critter p1 = aGroup[c1];
aCount--;
aGroup[c1] = aGroup[aCount];
return p1;
}

/**
* Main method, contructs the scenario and sets it off
* @param aArgs any command line arguments (ignored at present)
**/
public static void main(String aArgs[])
{
int lMin = 100000;
int lMax = 0;
int lTotal = 0;

for (int i = 0; i < TRIALS; i++)
{
Substitute sub = new Substitute();
sub.analyze();
boolean bStop = false;
while (!bStop)
{
sub.nextGeneration();
bStop = sub.analyze();
}
int lGen = sub.getGenerations();
if (lGen < lMin) { lMin = lGen;}
if (lGen > lMax) { lMax = lGen;}
lTotal += lGen;
System.out.println("Completed run: " + i + " Result: " + lGen);
}
System.out.println("Min: " + lMin + " Max: " + lMax +
" Average: " + ((lTotal + TRIALS/2)/ TRIALS) +
" (Trials: " + TRIALS + ")");
}
}

No comments: