/** -*- mode: c++; c-indent-level: 4; c++-member-init-indent: 8; comment-column: 35; -*-

   -----------------------------------------------------------------------------
   eoNDSorting.h
   (c) Maarten Keijzer, Marc Schoenauer, 2001

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Contact: todos@geneura.ugr.es, http://geneura.ugr.es
             Marc.Schoenauer@polytechnique.fr
             mkeijzer@dhi.dk
 */
//-----------------------------------------------------------------------------

#ifndef eoNDSorting_h
#define eoNDSorting_h

#include <EO.h>
#include <algorithm>
#include <eoPop.h>
#include <eoPerf2Worth.h>
#include <cassert>

/**
  Non dominated sorting, it *is a* std::vector of doubles, the integer part is the rank (to which front it belongs),
  the fractional part the niching penalty or distance penalty or whatever penalty you want to squeeze into
  the bits.
*/
template <class EOT>
class eoNDSorting : public eoPerf2WorthCached<EOT, double>
{
  public :

    using eoPerf2WorthCached<EOT, double>::value;
      eoNDSorting(bool nasty_flag_ = false)
          : nasty_declone_flag_that_only_is_implemented_for_two_objectives(nasty_flag_)
        {}


    eoNDSorting()
        : nasty_declone_flag_that_only_is_implemented_for_two_objectives(false)
        {}

    /** Pure virtual function that calculates the 'distance' for each element in the current front
        Implement to create your own nondominated sorting algorithm. The size of the returned std::vector
        should be equal to the size of the current_front.
    */
    virtual std::vector<double> niche_penalty(const std::vector<unsigned>& current_front, const eoPop<EOT>& _pop) = 0;

    void calculate_worths(const eoPop<EOT>& _pop)
    {
        // resize the worths beforehand
        value().resize(_pop.size());

        typedef typename EOT::Fitness::fitness_traits traits;

        switch (traits::nObjectives())
        {
            case 1:
                {
                    one_objective(_pop);
                    return;
                }
            case 2:
                {
                    two_objectives(_pop);
                    return;
                }
            default :
                {
                    m_objectives(_pop);
                }
        }
    }

private :

    /** used in fast nondominated sorting
        DummyEO is just a storage place for fitnesses and
        to store the original index
    */
    class DummyEO : public EO<typename EOT::Fitness>
    {
      public: unsigned index;
    };

    void one_objective(const eoPop<EOT>& _pop)
    {
        unsigned i;
        std::vector<DummyEO> tmp_pop;
        tmp_pop.resize(_pop.size());

        // copy pop to dummy population (only need the fitnesses)
        for (i = 0; i < _pop.size(); ++i)
        {
          tmp_pop[i].fitness(_pop[i].fitness());
          tmp_pop[i].index = i;
        }

        std::sort(tmp_pop.begin(), tmp_pop.end(), std::greater<DummyEO>());

        for (i = 0; i < _pop.size(); ++i)
        {
          value()[tmp_pop[i].index] = _pop.size() - i; // set rank
        }

        // no point in calculcating niche penalty, as every distinct fitness value has a distinct rank
    }

    /**
     * Optimization for two objectives. Makes the algorithm run in
     * complexity O(n log n) where n is the population size
     *
     * This is the same complexity as for a single objective
     * or truncation selection or sorting.
     *
     * It will perform a sort on the two objectives seperately,
     * and from the information on the ranks of the individuals on
     * these two objectives, the non-dominated sorting rank is determined.
     * There are then three nlogn operations in place: one sort per objective,
     * plus a binary search procedure to combine the information about the
     * ranks.
     *
     * After that it is a simple exercise to calculate the distance
     * penalty
     */

    void two_objectives(const eoPop<EOT>& _pop)
    {
                unsigned i;
        typedef typename EOT::Fitness::fitness_traits traits;
        assert(traits::nObjectives() == 2);

        std::vector<unsigned> sort1(_pop.size()); // index into population sorted on first objective

        for (i = 0; i < _pop.size(); ++i)
        {
            sort1[i] = i;
        }

        std::sort(sort1.begin(), sort1.end(), Sorter(_pop));

        // Ok, now the meat of the algorithm

        unsigned last_front = 0;

        double max1 = -1e+20;
        for (i = 0; i < _pop.size(); ++i)
        {
            max1 = std::max(max1, _pop[i].fitness()[1]);
        }

        max1 = max1 + 1.0; // add a bit to it so that it is a real upperbound

        unsigned prev_front = 0;
        std::vector<double> d;
        d.resize(_pop.size(), max1); // initialize with the value max1 everywhere

        std::vector<std::vector<unsigned> > fronts(_pop.size()); // to store indices into the front

        for (i = 0; i < _pop.size(); ++i)
        {
            unsigned index = sort1[i];

            // check for clones and delete them
            if (i > 0)
            {
                unsigned prev = sort1[i-1];
                if ( _pop[index].fitness() == _pop[prev].fitness())
                { // it's a clone, give it the worst rank!

                    if (nasty_declone_flag_that_only_is_implemented_for_two_objectives)
                        //declone
                        fronts.back().push_back(index);
                    else // assign it the rank of the previous
                        fronts[prev_front].push_back(index);
                    continue;
                }
            }

            double value2 = _pop[index].fitness()[1];

            if (traits::maximizing(1))
                value2 = max1 - value2;

            // perform binary search using std::upper_bound, a log n operation for each member
            std::vector<double>::iterator it =
                std::upper_bound(d.begin(), d.begin() + last_front, value2);

            unsigned front = unsigned(it - d.begin());
            if (front == last_front) ++last_front;

            assert(it != d.end());

            *it = value2; //update d
            fronts[front].push_back(index); // add it to the front

            prev_front = front;
        }

        // ok, and finally the niche penalty

        for (i = 0; i < fronts.size(); ++i)
        {
            if (fronts[i].size() == 0) continue;

            // Now we have the indices to the current front in current_front, do the niching
            std::vector<double> niche_count = niche_penalty(fronts[i], _pop);

            // Check whether the derived class was nice
            if (niche_count.size() != fronts[i].size())
            {
              throw std::logic_error("eoNDSorting: niche and front should have the same size");
            }

            double max_niche = *std::max_element(niche_count.begin(), niche_count.end());

            for (unsigned j = 0; j < fronts[i].size(); ++j)
            {
              value()[fronts[i][j]] = i + niche_count[j] / (max_niche + 1.); // divide by max_niche + 1 to ensure that this front does not overlap with the next
            }

        }

        // invert ranks to obtain a 'bigger is better' score
        rank_to_worth();
    }

    class Sorter
    {
        public:
            Sorter(const eoPop<EOT>& _pop) : pop(_pop) {}

            bool operator()(unsigned i, unsigned j) const
            {
                typedef typename EOT::Fitness::fitness_traits traits;

                double diff = pop[i].fitness()[0] - pop[j].fitness()[0];

                if (fabs(diff) < traits::tol())
                {
                    diff = pop[i].fitness()[1] - pop[j].fitness()[1];

                    if (fabs(diff) < traits::tol())
                        return false;

                    if (traits::maximizing(1))
                        return diff > 0.;
                    return diff < 0.;
                }

                if (traits::maximizing(0))
                    return diff > 0.;
                return diff < 0.;
            }

            const eoPop<EOT>& pop;
    };

    void m_objectives(const eoPop<EOT>& _pop)
    {
      unsigned i;

      //typedef typename EOT::Fitness::fitness_traits traits;

      std::vector<std::vector<unsigned> > S(_pop.size()); // which individuals does guy i dominate
      std::vector<unsigned> n(_pop.size(), 0); // how many individuals dominate guy i

      unsigned j;
      for (i = 0; i < _pop.size(); ++i)
      {
        for (j = 0; j < _pop.size(); ++j)
        {
          if (_pop[i].fitness().dominates(_pop[j].fitness()))
          { // i dominates j
            S[i].push_back(j); // add j to i's domination std::list

            //n[j]++; // as i dominates j
          }
          else if (_pop[j].fitness().dominates(_pop[i].fitness()))
          { // j dominates i, increment count for i, add i to the domination std::list of j
            n[i]++;

            //S[j].push_back(i);
          }
        }
      }

      std::vector<unsigned> current_front;
      current_front.reserve(_pop.size());

      // get the first front out
      for (i = 0; i < _pop.size(); ++i)
      {
        if (n[i] == 0)
        {
          current_front.push_back(i);
        }
      }

      std::vector<unsigned> next_front;
      next_front.reserve(_pop.size());

      unsigned front_index = 0; // which front are we processing
      while (!current_front.empty())
      {
        // Now we have the indices to the current front in current_front, do the niching
        std::vector<double> niche_count = niche_penalty(current_front, _pop);

        // Check whether the derived class was nice
        if (niche_count.size() != current_front.size())
        {
          throw std::logic_error("eoNDSorting: niche and front should have the same size");
        }

        double max_niche = *std::max_element(niche_count.begin(), niche_count.end());

        for (i = 0; i < current_front.size(); ++i)
        {
          value()[current_front[i]] = front_index + niche_count[i] / (max_niche + 1.); // divide by max_niche + 1 to ensure that this front does not overlap with the next
        }

        // Calculate which individuals are in the next front;

        for (i = 0; i < current_front.size(); ++i)
        {
          for (j = 0; j < S[current_front[i]].size(); ++j)
          {
            unsigned dominated_individual = S[current_front[i]][j];
            n[dominated_individual]--; // As we remove individual i -- being part of the current front -- it no longer dominates j

            if (n[dominated_individual] == 0) // it should be in the current front
            {
              next_front.push_back(dominated_individual);
            }
          }
        }

        front_index++; // go to the next front
        swap(current_front, next_front); // make the next front current
        next_front.clear(); // clear it for the next iteration
      }

      rank_to_worth();
    }

    void rank_to_worth()
    {
      // now all that's left to do is to transform lower rank into higher worth
      double max_fitness = *std::max_element(value().begin(), value().end());

      // but make sure it's an integer upper bound, so that all ranks inside the highest integer are the front
      max_fitness = ceil(max_fitness);

      for (unsigned i = 0; i < value().size(); ++i)
      {
        value()[i] = max_fitness - value()[i];
      }

    }
    public : bool nasty_declone_flag_that_only_is_implemented_for_two_objectives;
};

/**
  The original Non Dominated Sorting algorithm from Srinivas and Deb
*/
template <class EOT>
class eoNDSorting_I : public eoNDSorting<EOT>
{
public :
  eoNDSorting_I(double _nicheSize, bool nasty_flag_ = false) : eoNDSorting<EOT>(nasty_flag_), nicheSize(_nicheSize) {}

  std::vector<double> niche_penalty(const std::vector<unsigned>& current_front, const eoPop<EOT>& _pop)
  {
        std::vector<double> niche_count(current_front.size(), 0.);

        for (unsigned i = 0; i < current_front.size(); ++i)
        { // calculate whether the other points lie within the nice
          for (unsigned j = 0; j < current_front.size(); ++j)
          {
            if (i == j)
              continue;

            double dist = 0.0;

            for (unsigned k = 0; k < _pop[current_front[j]].fitness().size(); ++k)
            {
              double d = _pop[current_front[i]].fitness()[k] - _pop[current_front[j]].fitness()[k];
              dist += d*d;
            }

            if (dist < nicheSize)
            {
              niche_count[i] += 1.0 - pow(dist / nicheSize,2.);
            }
          }
        }

        return niche_count;
  }

  private :

  double nicheSize;
};

/** @brief Fast Elitist Non-Dominant Sorting Genetic Algorithm

  Adapted from Deb, Agrawal, Pratab and Meyarivan: A Fast Elitist
  Non-Dominant Sorting Genetic Algorithm for MultiObjective
  Optimization: NSGA-II KanGAL Report No. 200001

  Note that this class does not do the sorting per se, but the sorting
  of it worth_std::vector will give the right order

  The crowding distance is calculated as the sum of the distances to
  the nearest neighbours. As we need to return the penalty value, we
  have to invert that and invert it again in the base class, but such
  is life, sigh
*/
template <class EOT>
class eoNDSorting_II : public eoNDSorting<EOT>
{
  public:

    eoNDSorting_II(bool nasty_flag_ = false) : eoNDSorting<EOT>(nasty_flag_) {}

  typedef std::pair<double, unsigned> double_index_pair;

  class compare_nodes
  {
  public :
    bool operator()(const double_index_pair& a, const double_index_pair& b) const
    {
      return a.first < b.first;
    }
  };

  /// _cf points into the elements that consist of the current front
  std::vector<double> niche_penalty(const std::vector<unsigned>& _cf, const eoPop<EOT>& _pop)
  {
    typedef typename EOT::Fitness::fitness_traits traits;
    unsigned i;
    std::vector<double> niche_count(_cf.size(), 0.);


    unsigned nObjectives = traits::nObjectives(); //_pop[_cf[0]].fitness().size();

    for (unsigned o = 0; o < nObjectives; ++o)
    {

      std::vector<std::pair<double, unsigned> > performance(_cf.size());
      for (i =0; i < _cf.size(); ++i)
      {
        performance[i].first = _pop[_cf[i]].fitness()[o];
        performance[i].second = i;
      }

      std::sort(performance.begin(), performance.end(), compare_nodes()); // a lambda operator would've been nice here

      std::vector<double> nc(niche_count.size(), 0.0);

      for (i = 1; i < _cf.size()-1; ++i)
      { // calculate distance
        nc[performance[i].second] = performance[i+1].first - performance[i-1].first;
      }

      double max_dist = *std::max_element(nc.begin(), nc.end());

      // set boundary at max_dist + 1 (so it will get chosen over all the others
      nc[performance[0].second]     += max_dist + 1;
      nc[performance.back().second] += max_dist + 1;

      for (i = 0; i < nc.size(); ++i)
      {
        niche_count[i] += (max_dist + 1 - nc[i]);
      }
    }

    return niche_count;
  }
};

#endif
