// ---------------------------------------------------------------------------
//   GeneticAlgorithm.h (created on Tue Nov 08 01:08:35 BRT 2005)
// 
//   Genetic Algorithm File Fitter (gaffitter)
//
//   Copyright (C) 2005-2015 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/>.
//
// ---------------------------------------------------------------------------

#ifndef GeneticAlgorithm_hh
#define GeneticAlgorithm_hh

#include "../Input.h"
#include "../util/Random.h"
#include "../Optimizer.h"

#include <vector>
#include <iterator>

static const Params::Size_t WORST_FITNESS = 0.0;
static const Params::Size_t BEST_FITNESS = 1.0;

// ---------------------------------------------------------------------------
/**
 * @class Individual
 *
 * @brief This class represents an Individual, i.e., an entity representing a
 * candidate solution.
 *
 * Basically, an Individual holds a particular set of bins, where each bin is
 * a collection of items.
 */
class Individual {
public:
   /** 
    * @brief Create the individual with the worst possible fitness. */
   Individual(): m_fitness( WORST_FITNESS ) {}

   /**
    * @brief Returns the individual's fitness. */
   Params::Size_t Fitness() const { return m_fitness; }
   /**
    * @brief Sets the individual's fitness */
   void Fitness( Params::Size_t f ) { m_fitness = f; }

   /**
    * @brief Compare two individuals.
    *
    * First the fitness is compared; if they are equal, then compare their
    * genomes.
    */
   bool operator==( const Individual& i ) const
   {
      if( !AlmostEqual( Fitness(), i.Fitness() ) ) return false;

      return Genome() == i.Genome();
   }

   /**
    * @brief Compare the fitness of two individuals.
    */
   friend bool operator>( Individual &a, Individual &b ) 
   { 
      return a.m_fitness > b.m_fitness; 
   }

   /**
    * @brief Returns the individual's genome ( set of bins ).
    */
   BinSet& Genome() { return m_genome; }
   /**
    * @brief Returns the individual's genome ( set of bins ); const version.
    */
   const BinSet& Genome() const { return m_genome; }

   /**
    * @brief Returns the individual's ith genome (a bin).
    */
   Bin& Genome(int i) { return m_genome[i]; }
   /**
    * @brief Returns the individual's ith genome (a bin); const version.
    */
   const Bin& Genome(int i) const { return m_genome[i]; }

public:
   BinSet m_genome; /**< The genome is the genetic material (i.e., 
                         chromosomes) of an individual */
   Params::Size_t m_fitness; /**< The individual's fitness. */
};

// ---------------------------------------------------------------------------
/**
 * @class GeneticAlgorithm
 *
 * @brief This class implements a Genetic Algorithm system as search algorithm.
 *
 * This implementation is based on Falkenauer's HGGA (Hybrid Grouping Genetic 
 * Algorithm).
 *
 * In the HGGA, the genes represent each one a group of items, i.e., each gene
 * is treated as a bin and their items act as an unit, a building block;
 * therefore, the crossover operator does not mixes items on an individual
 * basis, but, rather, it combines groups of bins. Besides, HGGA uses a local
 * optimizer inspired on the Dominance Criterion of Martello and Toth, which
 * basically tries iteratively to replace a few items of a certain bin by
 * fewer items that fit better in. This procedure not only optimizes the bin,
 * but also eases the reallocation of the replaced items, since smaller items
 * are easier to fit.
 *
 * Reference:
 *
 * A Hybrid Grouping Genetic Algorithm for Bin Packing
 * http://citeseer.ist.psu.edu/falkenauer96hybrid.html
 */
class GeneticAlgorithm: public Optimizer {
public:
   /**
    * If not specified, the constructor will automatically adjust population
    * size and number of generations. The Random::Seed is also set.
    */
   GeneticAlgorithm( Input& );
private:
   /** Copies are not allowed. */
   GeneticAlgorithm( const GeneticAlgorithm& );
   /** Attributions are not allowed. */
   GeneticAlgorithm& operator=( const GeneticAlgorithm& );

public:

   /** 
    * @brief Genetic Algorithm search.
    *
    * GA searches for optimal (though not guaranteed) solutions by means of
    * Darwinian evolution.
    */
   void Evolve();
 
   /**
    * @brief Evaluate an individual.
    */
   bool Evaluate( int i ) { return Evaluate( m_individuals[i] ); }
   /**
    * @brief Evaluate an individual.
    */
   bool Evaluate( Individual& );

protected:
   /**
    * Create the population of individuals and put it in \ref m_individuals
    *
    * As usually in GA, the creation of individuals is basically a random
    * process. For each individual a new permutation over the m_files is
    * generated and then those randomized items are inserted via the First Fit
    * algorithm.
    */
   bool InitPopulation();

   /**
    * Runs the GA for one generation. Returns true if a perfect fit was found;
    * or the stop criterion was reached; false otherwise.
    *
    * A perfect fit occurs when either (1) just one bin is suffice to pack all
    * files; or (2) an individual reaches the maximum fitness (1.0).
    */
   bool Generation();

   /** Just for notation convenience. */
   Params::Size_t Fitness( int index ) const 
   { 
      return Fitness( m_individuals[index] ); 
   }

   /**
    * Returns how well is the given individual. The objective is to maximize
    * its fitness.
    */
   Params::Size_t Fitness( const Individual& i ) const { return i.Fitness(); }

   /** Select in [a,b] a random individual. */
   int Select( int a, int b ) const { return Random::Int( a, b ); }

   /** Select in [a,b] a random individual 'r' not equal 'p'. */
   int Select( int a, int b, int p ) const 
   { 
      int r = Select( a, b );
      
      // equals?
      if( r == p ) return r > a ? r - 1 : a + 1;

      return r; // Ok: 'b' not equal 'a'!
   }

   /** Select in [a,b] a random individual 'r' not equal 'p1' and 'p2'
    *  Required: b - a > 2 
    * */
   int Select( int a, int b, int p1, int p2 ) const 
   { 
      int r = Select( a, b );

      if( p1 > p2 ) std::swap( p1, p2 );

      if( r == p1 )
      {
         if( p1 == a ) return p1 + 1 == p2 ? r + 2 : r + 1;
         if( p1 > a ) return r - 1;
      }

      if( r == p2 )
      {
         if( p2 == b ) return p2 == p1 + 1 ? r - 2 : r - 1;
         if( p2 < b ) return r + 1;
      }

      return r;
   }
   
   /** 
    * @brief Promotes the crossover between two individuals, generating two children.
    *
    * Basically, the crossover involves:
    *    -# Selecting crossover points (sections) on both parents.
    *    -# Inserting the crossover section of a parent into the crossover
    *    section of the another parent, and vice-versa. Actually, this is done
    *    on the children, so the parents' genomes remain intact.
    *    -# After the insertion procedure, likely some old bins of the children
    *    will contain items that were again inserted by the new bins that came
    *    from the crossover section of the other parent. Thus, those old bins
    *    containing duplicate items will be removed and their items (just the
    *    non-duplicates) will be marked as "unassigned".
    *    -# Finally, the DominanceOptimizer is applied in order to try to
    *    reallocate the unassigned bins. The remaining items--i.e., those that
    *    could not be reallocated--will be inserted on children via
    *    FirstFitDecreasing method.
    */
   bool Crossover( unsigned, unsigned, unsigned, unsigned );

   /**
    * @brief Mutate the ith individual.
    *
    * The mutation operator removes a random bin, makes its items "unassigned",
    * calls @ref DominanceOptimizer to try to better reallocate those items and 
    * finally insert the remaining unassigned items via @ref FirstFitDecreasing.
    */
   void Mutate( int i );

   /** 
    * Choose an individual via tournament (need 2 or more competitors).  This
    * is a two-competitor optimized version.
    *
    * @sa TournamentN()
    */
   std::pair<int,int> Tournament2( int, int ) const;

   /** 
    * Choose an individual via tournament (need 2 or more competitors).
    * This is a three+ competitor version.
    *
    * @sa Tournament2()
    */
   std::pair<int,int> TournamentN( int, int ) const;

   /**
    * Pointer to @ref Tournament2() or @ref TournamentN(), depending on
    * the tournament size.
    */
   std::pair<int,int> (GeneticAlgorithm::*Tournament)( int, int ) const;

   /**
    * @brief Returns the ith individual.
    */
   Individual& Individuals( int i ) { return m_individuals[i]; }
   /**
    * @brief Returns the ith individual; const version.
    */
   const Individual& Individuals( int i ) const { return m_individuals[i]; }

   /**
    * @brief Returns the vector of individuals.
    */
   std::vector<Individual>& Individuals() { return m_individuals; }
   /**
    * @brief Returns the vector of individuals; const version.
    */
   const std::vector<Individual>& Individuals() const { return m_individuals; }
   
   std::vector<Individual> m_individuals; /**< The population (vector) of 
                                               individuals. */
   Individual m_best_individual; /**< The best individual so far. */

public:
   /**
    * @brief Allocate items via FirstFit Algorithm.
    *
    * The FirstFit algorithm fit a given item into the first bin able to
    * accommodate it.
    */
   void FirstFit( BinSet&, const std::vector<const SizeName*>& ) const;
   /**
    * @brief Allocate items via FirstFitDecreasing Algorithm.
    *
    * The FirstFitDecreasing algorithm first sort in descending order the
    * given list of items and then pass this sorted list to @ref FirstFit.
    */
   void FirstFitDecreasing( BinSet&, std::vector<const SizeName*>& ) const;
   /**
    * @brief Try to reallocate items via Dominance Criterion.
    *
    * Basically, the DominanceOptimizer algorithm tries to replace smaller
    * items with bigger (possibility fitter) items. It tries iteratively to
    * replace a few items of a certain bin by fewer items that fit better in.
    * This procedure not only optimizes the bin, but also eases the
    * reallocation of the replaced items, since smaller items are easier to
    * fit.
    */
   void DominanceOptimizer( BinSet& bs, std::vector<const SizeName*>& 
                            unassigned ) const;
private:
   /**
    * @brief Tries to replace one item per time via Dominance Optimizer.
    */
   int DominanceForOne( Bin& bin, std::vector<const SizeName*>& 
                        unassigned ) const;
   /**
    * @brief Tries to replace two items per time via Dominance Optimizer.
    */
   int DominanceForTwo( Bin& bin, std::vector<const SizeName*>& 
                        unassigned ) const;
   /**
    * @brief Tries to replace three items per time via Dominance Optimizer.
    */
   int DominanceForThree( Bin& bin, std::vector<const SizeName*>& 
                          unassigned ) const;
protected:
   /** 
    * Writes some information (like algorithm name and parameters) in
    * ostream object (usually cout).
    */
   std::ostream& Write( std::ostream& ) const;

private:
   int m_cur_gen; /**< Current generation. */
   unsigned m_tournament_size; /**< Tournament size. */
};

// ---------------------------------------------------------------------------
#endif
