Search code examples
c++c++11genetic-algorithmevolutionary-algorithmcrossover

One-point cross over in genetic algorithm


I am using one point cross over to cross two individual. Assume that I have two individual such as

I1='10010011' 
I2='11001101'

tmp_P is vector store two individual I1 and I2. I want to implement one point cross over in C++. Is it right?

This is algorithm description

fori=1 to N/2 (N is number of individual=2 in my case)
   if random[0,1]<=Pc //cross prob.
       pos=random_int[1,n-1]
       for k=pos+1 to n //Length of individual=8 in my case
          aux=tmp_P_i[k]
          tmp_P_i[k]=tmp_P_(i+N/2)[k]
          tmp_P_(i+N/2)[k]=aux;
       end
   end
end

My problem is I am confusing the index of pos. Is it get random from [0 to n-2]. Is it right?

 //Random integer in range [min max]
int random_int(int min, int max) //range : [min, max]
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(min, max);
    return dis(gen);
}
//One-point Crossover
vector< vector<int> > crossover(vector< vector<int> > tmp_P)
{
    int pos=0;
    for (int i=0;i<N/2;i++)
    {
        //If random number smaller than crossover probability then do Crossover
        if(RND()<=Pc)
        {
            pos=random_int(0,n-2);//Index in C++ from 0
            int aux=0;
            for (int k=pos+1;k<n;k++)
            {
                //swat
                aux=tmp_P[i][k];
                tmp_P[i][k]=tmp_P[i+N/2][k];
                tmp_P[i+N/2][k]=aux;
            }
        }
    }
    return tmp_P;
}

Solution

    • random_int

      For debugging purpose (repeatability) you shouldn't always call rd(). Moreover you are recreating the pseudo-RNG at every call.

      Call the random device just once and do everything else with a (randomly) seeded pseudo-RNG. As a bonus, you should store the seed value in a log file so you can later replay the pseudo-random sequence.

      It should be something like:

      int random_int(int min, int max)
      {
      #if defined(NDEBUG)
        static std::mt19937 gen(std::random_device());  // or thread_local  
      #else
        static std::mt19937 gen(1234);  // or, better, thread_local
      #endif
      
        std::uniform_int_distribution<> dis(min, max);
        return dis(gen);
      }
      
    • crossover

      • pos is correct (it's in the [0;n-2] range); the actual crossover position is in the [1;n-1] range (skipping the 0 index is correct since it would swap the entire genome).

        You can directly initialize pos with:

         unsigned pos = random_int(1, n-1);
        
         for (unsigned k = pos; k < n; ++k)
         { /* ... */ }
        

        this is simpler.

      • you could use the std::swap function

      • if variables are habitually assigned a meaningful value when they first appear in the code then there's no chance of them accidentally being used with a meaningless or uninitialised value (e.g. pos / aux, see Why declare a variable in one line, and assign to it in the next?)
      • if the individuals' length is fixed you can also consider std::bitset to store the genome

    So something like this should work:

    unsigned random_int(unsigned min, unsigned max)
    {
    #if defined(NDEBUG)
      static std::mt19937 gen(std::random_device());
    #else
      static std::mt19937 gen(1234u);
    #endif
    
      std::uniform_int_distribution<> dis(min, max);
      return dis(gen);
    }
    
    std::vector<std::vector<int>> crossover(std::vector<std::vector<int>> tmp_P)
    {
      const auto N = tmp_P.size();
      const auto n = tmp_P[0].size();
    
      for (unsigned i = 0; i < N/2; ++i)
      {
        assert(tmp_P[i].size() == n);
    
        // If random number smaller than crossover probability then do Crossover
        if (RND() <= Pc)
          for (unsigned k = random_int(1, n-1); k < n; ++k)
            std::swap(tmp_P[i][k], tmp_P[i + N/2][k]);
      }
    
      return tmp_P;
    }