// ---------------------------------------------------------------------------
// $Id: GeneticAlgorithm.cc 231 2008-08-14 04:51:19Z daaugusto $
//
//   GeneticAlgorithm.cc (created on Tue Nov 08 01:08:35 BRT 2005)
// 
//   Genetic Algorithm File Fitter (gaffitter)
//
//   Copyright (C) 2005-2008 Douglas A. Augusto
// 
// This file is part of gaffitter.
// 
// gaffitter is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
// 
// gaffitter is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with gaffitter; if not, see <http://www.gnu.org/licenses/>.
//
// ---------------------------------------------------------------------------

#include "GeneticAlgorithm.h"

#include <utility>
#include <algorithm>

// ---------------------------------------------------------------------------
/** 
 * @def RELAXED
 * @brief Don't try to optimize each possible permutation of items.
 *
 * When in RELAXED mode, the DominanceOptimizer will try a single replacement
 * from one, two or three random points (items). Currently, defining RELAXED
 * gives better score (performance and better optimization) in the benchmarks
 * than without defining it.
 **/
#define RELAXED

/**
 * @def STOP_LOSS_DIVERSITY
 * @brief Stop the evolutionary process when the diversity is lost.
 */
// #define STOP_LOSS_DIVERSITY

using namespace std;

// ---------------------------------------------------------------------------
GeneticAlgorithm::GeneticAlgorithm( Input& i ): Optimizer( i ), m_cur_gen( 0 ) 
{
   // Selection pressure [default = 2]
   if( m_params.m_ga_sel_pressure <= 2 ) 
   {
      m_tournament_size = 2;
      Tournament = &GeneticAlgorithm::Tournament2;
   }
   else // maximum = pop size
   {
      m_tournament_size = m_params.m_ga_sel_pressure;
      Tournament = &GeneticAlgorithm::TournamentN;
   }

   // Population size and generations are proportionally to ln (natural
   // logarithmic) of input size (number of files).
   if( m_params.m_ga_pop_size <= m_tournament_size ) 
       m_params.m_ga_pop_size = 
            max( m_tournament_size + 1, 
            static_cast<unsigned>( 20 * log( (float) m_files.size() + 1 ) ) );
    
   if( m_params.m_ga_num_gens <= 0 ) 
       m_params.m_ga_num_gens = 
              static_cast<unsigned>( 50 * log( (float) m_files.size() + 1 ) );

   // reserve the correct size of the population
   m_individuals.reserve( m_params.m_ga_pop_size );

   m_params.m_ga_seed = Random::Seed( m_params.m_ga_seed ); 
}

// ---------------------------------------------------------------------------
bool 
GeneticAlgorithm::Evaluate( Individual& ind )
{ 
   ind.m_fitness = Optimizer::Evaluate( ind.m_genome );

   if( ind > m_best_individual ) 
   {
      m_best_individual = ind;

      if( m_params.m_verbose )
         cout << " [Gen " << m_cur_gen << ", bins: " << 
                 m_best_individual.m_genome.Count() << ", fitness: " << 
                 m_best_individual.Fitness() << "] " << flush;
   }

   /* If an individual reached the maximum fitness of 1.0 or if just a
    * bin is sufficient to pack all items/files, then returns "true";
    * i.e., the termination criterion was satisfied. Also, if "stopping
    * when the theoretical minimum number of bins is reached" is specified,
    * then stop if this is the case. */
   if( ind.m_fitness >= BEST_FITNESS || ind.m_genome.Count() == 1 || 
      (m_params.m_theoretical && ind.m_genome.Count() <= m_params.m_bins_theo) )

      return m_best_individual = ind, true;

   return false;
}

// ---------------------------------------------------------------------------
void
GeneticAlgorithm::FirstFit( BinSet& bins, const vector<const SizeName*>& sn ) const
{
   for( unsigned u = 0; u < sn.size(); ++u )
   {
      // find the first sufficiently free bin; insert another bin if necessary
      unsigned i = 0;
      for( ; i < bins.Count(); ++i )
      {
         if( bins[i].Free() >= sn[u]->Size() ) 
         {
            bins[i].AddItem( sn[u] );
            break;
         }
      }

      // Hmmm, it is necessary to add a new bin
      if( i == bins.Count() ) bins.AddBin( m_params.m_target ).AddItem( sn[u] );
   }
}

// ---------------------------------------------------------------------------
void
GeneticAlgorithm::FirstFitDecreasing( BinSet& bins, vector<const SizeName*> 
                                                      &unassigned ) const
{
   if( unassigned.empty() ) return;

   // sort (descending order) the bin of unused items
   sort( unassigned.begin(), unassigned.end(), SizeName::CmpSizeRev );

   FirstFit( bins, unassigned );
}

// ---------------------------------------------------------------------------
void
GeneticAlgorithm::Evolve()
{
   // print the header
   if( m_params.m_verbose ) cout << *this;

   // ---- Evolving...
   // Initialize the GA population
   if( m_params.m_verbose ) cout << "> ";

   bool optimum_found = InitPopulation();
   while( !optimum_found && m_cur_gen < m_params.m_ga_num_gens )
   {
      if( m_params.m_verbose ) cout << "." << flush;
      optimum_found = Generation();
   }

   if( m_params.m_verbose ) cout << " <" << endl << flush;

   // pick the best and put it in m_solution as a BinSet datatype
   m_solution = &m_best_individual.m_genome;
}

// ---------------------------------------------------------------------------
bool
GeneticAlgorithm::InitPopulation()
/* create the individuals by generating permutations and using the
   First Fit algorithm. */
{
   ++m_cur_gen; // the initial population is the first generation

   for( unsigned j = 0; j < m_params.m_ga_pop_size; ++j )
   {
      // Insert a new individual in population (m_individuals)
      m_individuals.push_back( Individual() );

      // Generate a random permutation of the input items/files
      for( unsigned i = 0; i < m_files.size(); ++i ) 
         swap( m_files[i], m_files[Random::Int( 0, m_files.size() - 1 )] );

      // Add the above permutation via FirstFit algorithm
      FirstFit( m_individuals.back().m_genome, m_files );

      // Evaluate (store the fitness) the newly created individual
      if( Evaluate( m_individuals.back() ) ) return true;
   }

   // No optimum solution found yet
   return false;
}

// ---------------------------------------------------------------------------
bool
GeneticAlgorithm::Generation()
{
   const int MAX_TRIES = 10;

   // increment the generation counter
   ++m_cur_gen;

   unsigned i = 0;
   while( i < m_individuals.size() )
   {
      if( Random::Probability( m_params.m_ga_cross_prob ) )
      {
         // select daddy and mommy and the losers (will be replaced by the children)
         int tries = MAX_TRIES;
         pair<int,int> dad, mom;
         do
         {
            dad = (this->*Tournament)( 0, m_individuals.size() / 2 - 1 );
            mom = (this->*Tournament)( m_individuals.size() / 2, m_individuals.size() - 1 );
         }
         while( Individuals(dad.first) == Individuals(mom.first) && tries-- > 0 );

         // Crossover dad.first and mom.first and put the offspring into
         // dad.second and mom.second
         if( Crossover( dad.first, mom.first, dad.second, mom.second ) ) return true;

         // Mutate the recently generated offspring with probability
         // m_params.m_ga_mut_prob
         if( Random::Probability( m_params.m_ga_mut_prob ) ) Mutate( dad.second );
         if( Random::Probability( m_params.m_ga_mut_prob ) ) Mutate( mom.second );

         if( Evaluate( dad.second ) || Evaluate( mom.second ) ) return true;

         // Mutate identical children/brothers
         tries = MAX_TRIES;
         while( tries-- > 0 )
         {
            if( Individuals(dad.second) == Individuals(dad.first) )
            {
               if( Mutate(dad.second), Evaluate(dad.second) ) return true;
               continue;
            }

            if( Individuals(mom.second) == Individuals(mom.first) )
            {
               if( Mutate(mom.second), Evaluate(mom.second) ) return true;
               continue;
            }

            if( Individuals(dad.second) == Individuals(mom.second) )
            {
               if( Random::Probability( 0.5 ) )
               {
                  if( Mutate( dad.second ), Evaluate( dad.second ) ) return true;
               }
               else 
                  if( Mutate( mom.second ), Evaluate( mom.second ) ) return true;

               continue;
            }

            break;
         }

         ++++i;
      }
      else // Reproduction (cloning)
      {
         // Select a relatively good individual
         pair<int,int> sel = (this->*Tournament)( 0, m_individuals.size() - 1 );

         // Replace the worst of the tournament by a clone of the winner
         m_individuals[sel.second] = m_individuals[sel.first];

         // Mutate the clone with probability m_params.m_ga_mut_prob
         if( Random::Probability( m_params.m_ga_mut_prob ) ) 
         {
            if( Mutate( sel.second ), Evaluate( sel.second ) ) return true;
         }

         ++i;
      }
   }

   return false;
}

// ---------------------------------------------------------------------------
pair<int,int> 
GeneticAlgorithm::Tournament2( int from, int to ) const
{
   int sel1 = Select( from, to );
   int sel2 = Select( from, to, sel1 );

   // best and worse index, respectively
   if( Fitness(sel1) > Fitness(sel2) )
      return make_pair( sel1, sel2 );
   else
      return make_pair( sel2, sel1 );
}

// ---------------------------------------------------------------------------
pair<int,int> 
GeneticAlgorithm::TournamentN( int from, int to ) const
{
   vector<pair<Params::Size_t,int> > candidates;

   int sel = Select( from, to );
   candidates.push_back( make_pair( Fitness( sel ), sel ) );

   for( unsigned i = 1; i < m_tournament_size; ++i )
   {
      sel = Select( from, to, sel );
      candidates.push_back( make_pair( Fitness(sel), sel ) );
   }

   // sort by fitness (more is better)
   sort( candidates.begin(), candidates.end() );

   // best and worse index, respectively
   return make_pair( candidates.back().second, candidates.front().second );
}

// ---------------------------------------------------------------------------
bool
GeneticAlgorithm::Crossover( unsigned dad, unsigned mom, 
                             unsigned child1, unsigned child2 ) 
{
#ifdef STOP_LOSS_DIVERSITY
   static int diversity = m_params.m_ga_pop_size;
#endif

   /* 1) Select a crossover region (two points) for each parent */

      // Dad's crossover points
   pair<unsigned, unsigned> points[] = 
       { pair<unsigned, unsigned>( 0, 0 ), pair<unsigned, unsigned>( 0, 0 ) };
   unsigned parents[]  = { dad, mom };
   unsigned children[] = { child1, child2 };

   points[0].first  = Select( 0, m_individuals[dad].m_genome.Count() - 1 );
   points[0].second = Select( 0, m_individuals[dad].m_genome.Count() - 1, 
                              points[0].first );

   if( points[0].second < points[0].first ) 
      swap( points[0].first, points[0].second );

      // Mom's crossover points
   points[1].first  = Select( 0, m_individuals[mom].m_genome.Count() - 1 );
   points[1].second = Select( 0, m_individuals[mom].m_genome.Count() - 1, 
                              points[1].first );

   if( points[1].second < points[1].first ) 
      swap( points[1].first, points[1].second );

   for( short unsigned i = 0; i <= 1; ++i ) // 0 = dad, 1 = mom
   {
       /* 2) Insert dad's genes into the crossover segment of mom 
         (actually the mom's clone) and vice-versa. The old bins having
         elements also belonging to the new bins will be removed and
         their elements will be considered 'unassigned'. */

      // The temporary bin of unassigned items of unlimited capacity
      vector<const SizeName*> unassigned;

      // Reset the losers. They will be replaced by the children
      m_individuals[children[i]].m_genome.DelAllBins();


      // Create the children by cloning Dad and injecting the crossover
      // region of Mom--and vice-versa.
      for( unsigned j = 0; j < points[i].first; ++j ) // 'j' is the gene index
      {
         if( !Intersect( m_individuals[parents[i]].m_genome[j],
                         m_individuals[parents[1 - i]].m_genome.begin() + 
                         points[1 - i].first, 
                         m_individuals[parents[1 - i]].m_genome.begin() + 
                         points[1 - i].second + 1,
                         unassigned ) )
         {
            m_individuals[children[i]].m_genome.AddBin( 
                                      m_individuals[parents[i]].m_genome[j] );
         }
      }

      // The crossover region of Mom
      for( unsigned j = points[1 - i].first; j <= points[1 - i].second; ++j ) 
                                                   // 'j' is the gene index
      {
         m_individuals[children[i]].m_genome.AddBin(
                                  m_individuals[parents[1 - i]].m_genome[j] );
      }

      // Cloning the tail of Dad
      for( unsigned j = points[i].first; 
                         j < m_individuals[parents[i]].m_genome.Count(); ++j ) 
      {
         if( !Intersect( m_individuals[parents[i]].m_genome[j],
                          m_individuals[parents[1 - i]].m_genome.begin() + 
                          points[1 - i].first, 
                          m_individuals[parents[1 - i]].m_genome.begin() + 
                          points[1 - i].second + 1,
                          unassigned ) )
         {
            m_individuals[children[i]].m_genome.AddBin( 
                                      m_individuals[parents[i]].m_genome[j] );
         }
      }

#ifdef STOP_LOSS_DIVERSITY
      if( unassigned.empty() )
      {
         if( --diversity <= 0 ) return true;
      }
      else diversity = m_params.m_ga_pop_size;
#endif

      /* 3) Apply Dominance Optimization */
      DominanceOptimizer( m_individuals[children[i]].m_genome, unassigned );

      /* 4) Apply First Fit Decreasing */
      FirstFitDecreasing( m_individuals[children[i]].m_genome, unassigned );
   }

   return false; // no stop yet
}

// ---------------------------------------------------------------------------
void
GeneticAlgorithm::Mutate( int ind )
{
#ifdef INVERSION
   int p1 = Select( 0, Individuals(ind).Genome().Count() - 1 );
   int p2 = Select( 0, Individuals(ind).Genome().Count() - 1, p1 );

   swap( Individuals(ind).Genome(p1), Individuals(ind).Genome(p2) );
#endif

   // Select a random bin
   int bin = Random::Int( 0, Individuals(ind).Genome().Count() - 1 );

   // Mark its items as "unassigned"
   vector<const SizeName*> unassigned( Individuals(ind).Genome(bin).begin(),
                                       Individuals(ind).Genome(bin).end() );

   // Remove the selected bin
   Individuals(ind).Genome().DelBin( bin );

   // Calls Dominance Optimizer (tries better reallocation)
   DominanceOptimizer( Individuals(ind).Genome(), unassigned );

   // Apply First Fit Decreasing (reallocate the remaining items)
   FirstFitDecreasing( Individuals(ind).Genome(), unassigned );
}

// ---------------------------------------------------------------------------
void
GeneticAlgorithm::DominanceOptimizer( BinSet& bs, vector<const SizeName*>& 
      unassigned ) const
{
   if( unassigned.empty() ) return;

   sort( unassigned.begin(), unassigned.end(), SizeName::CmpSizeRev );

   bool improved;
   do {
      improved = false;

      for( unsigned i = 0; i < bs.Count(); ++i )
      {
         if ( bs[i].Count() >= 3 ) 
            improved |= DominanceForThree( bs[i], unassigned );
         if ( bs[i].Count() >= 2 ) 
            improved |= DominanceForTwo( bs[i], unassigned );
         if ( bs[i].Count() >= 1 ) 
            improved |= DominanceForOne( bs[i], unassigned );
      }
   } while( improved ); // stop the loop if there is no improvement
}

// ---------------------------------------------------------------------------
int
GeneticAlgorithm::DominanceForOne( Bin& bin, 
                                   vector<const SizeName*>& unassigned ) const
{
   if( unassigned.empty() ) return 0;

#ifndef RELAXED
   int improved = 0;
   for( int one = 0; one < bin.Count(); ++one )
   {
#else
      int one = Select( 0, bin.Count() - 1 );
#endif
      for( unsigned j = 0; j < unassigned.size(); ++j )
      {
         if( unassigned[j]->Size() > bin[one]->Size() )
         {
            if( unassigned[j]->Size() - bin[one]->Size() <= bin.Free() )
            {
               const SizeName* tmp_item = unassigned[j];

               // remove unassigned[j] item from the vector of unassigned
               unassigned.erase( unassigned.begin() + j );

               // insert bs[i][0] into unassigned maintaining the sorting order
               vector<const SizeName*>::iterator it = lower_bound(
                               unassigned.begin(), unassigned.end(), bin[one], 
                               SizeName::CmpSizeRev );

               unassigned.insert( it, bin[one] );

               bin.ReplaceItem( one, tmp_item );

#ifndef RELAXED
               ++improved;
               break;
#else
               return 1;
#endif
            }
         }
         else break; // unassigned is sorted
      }
#ifndef RELAXED
   }
   return improved;
#else
   return 0;
#endif

}

// ---------------------------------------------------------------------------
int
GeneticAlgorithm::DominanceForTwo( Bin& bin, 
                                   vector<const SizeName*>& unassigned ) const
{
   if( unassigned.empty() ) return 0;

#ifndef RELAXED
   int improved = 0;
   for( int one = 0; one < bin.Count() - 1; ++one )
   {
      for( int two = one + 1; two < bin.Count(); ++two )
      {
#else
         int one = Select( 0, bin.Count() - 1 );
         int two = Select( 0, bin.Count() - 1, one );

         if( one > two ) swap( one, two );
#endif
         Params::Size_t sum = bin[one]->Size() + bin[two]->Size();
         for( unsigned j = 0; j < unassigned.size(); ++j )
         {
            if( unassigned[j]->Size() >= sum )
            {
               if( unassigned[j]->Size() - sum <= bin.Free() )
               {
                  const SizeName* tmp_item = unassigned[j];

                  // remove unassigned[j] item from the vector of unassigned
                  unassigned.erase( unassigned.begin() + j );

                  // insert one and two into unassigned maintaining the
                  // sorting order
                  vector<const SizeName*>::iterator it = lower_bound(
                               unassigned.begin(), unassigned.end(), bin[one], 
                               SizeName::CmpSizeRev );

                  unassigned.insert( it, bin[one] );

                  it = lower_bound( unassigned.begin(), unassigned.end(), bin[two], 
                                    SizeName::CmpSizeRev ); 

                  unassigned.insert( it, bin[two] );

                  // remove one and two from bs
                  bin.DelItem( two );
                  bin.DelItem( one );

                  // assign the fitter item
                  bin.AddItem( tmp_item );

#ifndef RELAXED
                  // reset one and two
                  one = 0; two = 1;

                  ++improved; // it may be very expensive!!!
                  break;
#else
                  return 1;
#endif
               }
            }
            else break; // unassigned is sorted
         }
#ifndef RELAXED
      }
   }
   return improved;
#else
   return 0;
#endif

}

// ---------------------------------------------------------------------------
int
GeneticAlgorithm::DominanceForThree( Bin& bin, 
                                   vector<const SizeName*>& unassigned ) const
{
   if( unassigned.size() < 2 ) return 0;

#ifndef RELAXED
   int improved = 0;
   for( int one = 0; one < bin.Count() - 2; ++one )
   {
      for( int two = one + 1; two < bin.Count() - 1; ++two )
      {
         for( int three = two + 1; three < bin.Count(); ++three )
         {
#else
            int one = Select( 0, bin.Count() - 1 );
            int two = Select( 0, bin.Count() - 1, one );
            int three = Select( 0, bin.Count() - 1, one, two );

            if( one > two ) swap( one, two );
            if( two > three) swap( two, three );
            if( one > two ) swap( one, two );
#endif
            Params::Size_t sum = bin[one]->Size() + bin[two]->Size() + bin[three]->Size();

            for( unsigned j = 0; j < unassigned.size() - 1; ++j )
            {
               for( unsigned k = j + 1; k < unassigned.size(); ++k )
               {
                  Params::Size_t sum_u = unassigned[j]->Size() + unassigned[k]->Size();

                  if( sum_u >= sum )
                  {
                     if( sum_u - sum <= bin.Free() )
                     {
                        const SizeName* tmp_item_j = unassigned[j];
                        const SizeName* tmp_item_k = unassigned[k];

                        // remove items from the vector unassigned
                        unassigned.erase( unassigned.begin() + k );
                        unassigned.erase( unassigned.begin() + j );

                        // insert one, two and three into unassigned maintaining the
                        // sorting order
                        vector<const SizeName*>::iterator it = lower_bound(
                              unassigned.begin(), unassigned.end(), bin[one], 
                              SizeName::CmpSizeRev );
                        unassigned.insert( it, bin[one] );

                        it = lower_bound( unassigned.begin(), unassigned.end(), bin[two], 
                              SizeName::CmpSizeRev ); 
                        unassigned.insert( it, bin[two] );

                        it = lower_bound( unassigned.begin(), unassigned.end(), bin[three], 
                              SizeName::CmpSizeRev ); 
                        unassigned.insert( it, bin[three] );

                        // remove one and two from bs
                        bin.DelItem( three );
                        bin.DelItem( two );
                        bin.DelItem( one );

                        // assign the fitter item
                        bin.AddItem( tmp_item_j );
                        bin.AddItem( tmp_item_k );

#ifndef RELAXED
                        // reset the variables
                        one = 0; two = 1; three = 2;

                        ++improved;

                        break;
#else
                        return 1; // juste one improvement
#endif
                     }
                  } else break; // unassigned is sorted
               }
               break;
            }
#ifndef RELAXED
         }
      }
   }
   return improved;
#else
   return 0;
#endif

}

// ---------------------------------------------------------------------------
ostream& 
GeneticAlgorithm::Write( ostream& s ) const
{
   s << "\n> -----------------------------------"
     << "\n> Genetic Algorithm (steady-state)"
     << "\n> -----------------------------------"
     << "\n> Max number of generations: " << m_params.m_ga_num_gens
     << "\n> Population size: " << m_params.m_ga_pop_size
     << "\n> Crossover probability: " << m_params.m_ga_cross_prob
     << "\n> Mutation probability: " << m_params.m_ga_mut_prob
     << "\n> Tournament size: " << m_tournament_size
     << "\n> Seed: " << m_params.m_ga_seed 
     << endl;

   Optimizer::Write( s );

   s << flush << endl;

   return s;
}

// ---------------------------------------------------------------------------
