Sample GA implementation - Maximise function f(x) over some interval
// forward declarations:
double decode ( Chromosome );
double normalise ( double );
// global variables:
World w; // the world (changes each generation)
Individual bestever; // the best solution ever seen over all the generations
//--- maximise this function: ------------------------------
double f ( double x )
{
return ( sin(x)+sin(2*x)+sin(5*x)+cos(x) );
}
//--- over this interval: ----------------------------------
const double low = 1.0;
const double high = 10.0;
// to graph the function,
// what y axis should the plot use? (rough guide)
const double lowy = -2.5;
const double highy = 4.0;
// add this to all y to make them positive:
const double cmin = 2.5;
Interpret the Chromosome as a number x. Return f(x) as the fitness (except make sure it is positive):
double positiveFitness ( Chromosome a )
// what the chromosome means is entirely up to the application.
// here I interpret it as encoding a double in the range 0 to (2^l - 1),
// which I can normalise so that it encodes a double in the range I'm interested in.
// (what this means is that l is actually a measure of the number of decimal places)
{
double x = decode(a);
double y = f(x);
// must return a positive fitness value.
// we can scale it/normalise it any way we like it
// only the GA library sees this
// (all the statistics routines in the application deal with f(x), not 'fitness' )
return ( y + cmin );
}
double decode ( Chromosome a )
// a is the binary expression of a double
{
double no = 0.0;
double power = 1; // power goes 2^0, 2^1, 2^2 ..
for ( int i=l; i>=1; i-- ) // work right to left
{
if ( a[i] )
no = no + power;
power = power * 2;
}
// we now have a number no, in the range 0 to (2^l - 1)
// can interpret this as encoding a number in the range we're interested in:
return ( normalise(no) );
}
// calculate these only once (more efficient):
const double max = (pow(2,l)-1);
const double interval = (high-low);
double normalise ( double no )
{ // no comes in as [0..max]..
double p = no / max; // normalise it to [0..1]..
double x = low + (p*interval); // then normalise that to [low..high]
return x;
}
Note there is no "encode" function - numbers are generated automatically at the string level, we never manually put them there. "decode" is only so that we can see what they've done.
User application reports:
// the application's statistical reports deal in concepts of x and f(x).
// they understand what the 'string' and 'fitness' actually mean.
detailedReport ( int t, ostream& stream )
{
w.runStatistics();
stream << "\n\n\n";
stream << "t=" << t << "\n";
stream << " # string x f(x) \n";
stream << "-------------------------------------------------\n";
for ( int i=1; i<=n; i++ )
{
sprintf ( buf, "%3d ", i );
Chromosome c = w.A[i].a;
stream << buf << c;
double x = decode(c);
sprintf ( buf, " %0.3f %0.3f",
x, f(x) );
stream << buf;
if ( w.A[i].fitness > w.averageFitness )
stream << " +";
stream << "\n";
}
stream << "-------------------------------------------------\n";
}
besteverPrint ( int t, ostream& stream )
{
sprintf ( buf, "%10d ", t );
stream << buf;
stream << bestever.a;
double x = decode(bestever.a);
sprintf ( buf, " %0.3f %0.3f \n",
x, f(x) );
stream << buf;
}
besteverHeader ( ostream& stream )
{
// a report that only the application can do,
// interpreting w in the light of what the strings actually mean ( x and f(x) )
stream << "--- progressive list of best solutions found ----------- \n";
stream << "generation string x f(x) \n";
w.runStatistics();
bestever = w.best;
besteverPrint ( 1, stream );
}
Boolean newRecord()
{
w.runStatistics();
return ( w.best.fitness > bestever.fitness );
}
The main function:
main ( int argc, char **argv )
{
int ARG = atoi ( argv[1] );
w.randomise();
besteverHeader ( cout );
for ( int t=1; t<=ARG; t++ )
{
if ( newRecord() )
{
bestever = w.best;
besteverPrint ( t, cout );
}
// detailedReport ( t, debugfile );
// w.detailedReport ( t, debugfile );
// w.summaryReport ( t, debugfile );
w.next();
}
}
No comments:
Post a Comment