Saturday 8 March 2014

Basic genetic algorithm


Basic genetic algorithm


// generic GA
// single linear binary chromosome
// interpretation of it is up to the program - no interpretation is made here



// I thought of randomising all objects in their constructors,
// but this is probably unnecessary half the time
// (and could even perhaps lead to infinite loops).
// Instead, I have a hierarchy of randomising,
// so calling the randomise() function on a Population
// randomises all the individuals within it, etc.
const int l = 30;               // length of chromosome
const int n = 50;               // size of population (should be even)
const double pc = 0.8;          // probability of crossover
const double pm = 0.005;        // probability of mutation

A Chromosome:


typedef Boolean Allele;         // alphabet is 0,1 (k=2)


// my own array, with 1..n indexing:
class AlleleArray
{
public:
 Allele secret[l];
 Allele& operator[] ( int i ) { return secret[i-1]; }
  // return reference because x[] might appear on lhs of equation
};


class Chromosome : public AlleleArray
{
public: 
 Chromosome& operator= ( Chromosome& c )
 { 
  for ( int i=1; i<=l; i++ )
    { (*this)[i] = c[i]; }
  return *this;         
 }

 randomise()
 { 
  for ( int i=1; i<=l; i++ )
    { (*this)[i] = r.flip(0.5); }       // a fair toss
 } 

 friend ostream& operator<< ( ostream& stream, Chromosome& c)
 {
  for ( int i=1; i<=l; i++ ) 
  { 
   Boolean b = c[i];
   stream << b;
  } 
  return stream;
 }
};      

The main, problem-specific, forward declaration:


double positiveFitness ( Chromosome );  

        // how fit is this Chromosome?
        // the user application must return a positive fitness

An Individual:


class Individual
{
public:
 Chromosome     a;              // a[1] .. a[l]
 int            parent1;
 int            parent2;
 int            crossoversite;
 double         fitness;

 updateFitness()                        // new chromosome..
 {
  fitness = positiveFitness(a);         // ask the application to rate it
  if ( fitness < 0 )
  {
        // return error 
  }
 }

 Individual& operator= ( const Individual& x )
 {
  a = x.a;
  parent1 = x.parent1;
  parent2 = x.parent2;
  crossoversite = x.crossoversite;
  updateFitness();
  return *this;
 }

 makeMe ( Chromosome c, int p1, int p2, int x )
 {
  a = c;
  parent1 = p1;
  parent2 = p2;
  crossoversite = x;
  updateFitness();
 }

 randomise()
 { 
  a.randomise();
  parent1 = 0;
  parent2 = 0;
  crossoversite = 0;
  updateFitness();
 }

 friend ostream& operator<< ( ostream& stream, Individual& x)
 {
  sprintf ( buf, "(%3d,%3d)", x.parent1, x.parent2 );
  stream << buf;
  if ( x.crossoversite == l )
    sprintf ( buf, ",  -   " );         // there was no crossover
  else
    sprintf ( buf, ", %3d  ", x.crossoversite );
  stream << buf;
  stream << x.a;
  sprintf ( buf, " %8.3f", x.fitness );
  stream << buf;
  return stream;
 }
};


A Population:


class IndividualArray
{
public:
  Individual secret[n];
  Individual& operator[] ( int i ) { return secret[i-1]; }
};


class Population : public IndividualArray
{
public:
 Population& operator= ( Population& p )
 {
  for ( int i=1; i<=n; i++ )
   { (*this)[i] = p[i]; } 
  return *this;
 }

 randomise()
 {
  for ( int i=1; i<=n; i++ )
   { (*this)[i].randomise(); } 
 }

 Population() 
 {
  if ( (n % 2) != 0 )
  {
        // return error 
  }
 }
};






class World
{
public:
 Population     A;                      // A[1] .. A[n]
// these statistics calculated by runStatistics:
 Individual     best;
 double         worstFitness, averageFitness, sumFitness;


                randomise() { A.randomise(); runStatistics(); }

 int            select();               
                crossover ( int, int ); // deposits output in:
 Individual     child1,child2;  
 Allele         mutate ( Allele );
};


Select a random individual for reproduction. Fittest most probable to be selected:




int World :: select()
// actually called multiple times
// each time just selects one A[i] for reproduction
{
 double point = r.prob() * sumFitness; 
        // random point between 0 and sigma fitnesses ('spin the wheel')
        // find the individual intersecting this point
 double partial = 0.0;
 int i = 0;                     
 while ( (partial<=point) && (i < n) )
 {
  i++;
  partial = partial + A[i].fitness;
 }
 // the loop has stopped because either partial has just crossed the line, or i=n.
 // either way:
 return(i);
}

Mix up the 2 parents' genes:


World :: crossover ( int parent1, int parent2 )
// writes output to child1,child2
{
// first build the chromosomes..
 Chromosome p1 = A[parent1].a;
 Chromosome p2 = A[parent2].a;
 Chromosome c1;
 Chromosome c2;
 int site,i;
 
 if ( r.flip(pc) )              // flip coin weighted with pc
   site = r.restricted(1,l-1);  // crossover - random cut, from after 1 to after l-1
 else 
   site = l;                    // no crossover - 'cut' after l
// the cut comes after site,
// so elements 1 to site go to one side, site+1 to l into other:
 for ( i=1; i<=site; i++ ) 
 {
  c1[i] = mutate(p1[i]);
  c2[i] = mutate(p2[i]);
 }
 for ( i=site+1; i<=l; i++ ) 
 {
  c1[i] = mutate(p2[i]);
  c2[i] = mutate(p1[i]);
 }

// then build the individuals..
 child1.makeMe ( c1, parent1, parent2, site );
 child2.makeMe ( c2, parent1, parent2, site );
}

Certain probability of mutation:


Allele World :: mutate ( Allele a )
// mutate allele a with probability pm
{
 if ( r.flip(pm) )
  return (!a);
 else
  return (a);
}

Build a new population from the current one:


World :: next()
// replace population A
{
 runStatistics();       // just for safety, make sure we are up to date with A
 Population temp;
 for ( int i=1; i<=n-1; i=i+2 )         // build in pairs - temp(1,2), temp(3,4), .. temp(n-1,n)
 {
  crossover ( select(), select() );     // select two mates and build 2 new children
    temp[i] = child1;
  temp[i+1] = child2;
 }
// now we are finished with the old A. replace it:
 A = temp;
}

World :: runStatistics()
// update various statistics about myself
{
 best           = A[1];
 worstFitness   = A[1].fitness;
 sumFitness     = A[1].fitness;
 for ( int i=2; i<=n; i++ )
 {
  double f = A[i].fitness;
  sumFitness = sumFitness + f;
  if ( f < worstFitness ) worstFitness = f;
  if ( f > best.fitness ) best = A[i];
 }
 // these all now fully calculated.
 // all that remains is:
 averageFitness = sumFitness / n;
}



// the GA library's statistical reports are only concerned with the GA internals.
// they only deal in concepts of 'string' and 'fitness'
// they have no idea why that string has that fitness,
// or what it means:
World :: detailedReport ( int t, ostream& stream )
{
 runStatistics();
 stream << "\n\n\n";
 stream << "t=" << t << "\n";
 stream << "  #  parents,xsite                          string  fitness \n";
 stream << "------------------------------------------------------------\n";
 for ( int i=1; i<=n; i++ )
 {
  sprintf ( buf, "%3d ", i );
  stream << buf << A[i];
  if ( A[i].fitness > averageFitness )
    stream << " +";
  stream << "\n";
 }
 stream << "------------------------------------------------------------\n";
}

World :: summaryReport ( int t, ostream& stream )
// (the outside world tells me what time (i.e. what generation) it is)
{
 runStatistics();
 sprintf ( buf, "t=%-3d worst=%0.3f average=%0.3f best=( ",
            t, worstFitness, averageFitness );
 stream << buf;
 stream << best.a;
 sprintf ( buf, " , %0.3f ) \n",
            best.fitness );
 stream << buf;
}

No comments:

Post a Comment

 

FACEBOOK PAGE

SKETCHES & PAINTINGS