//  OMOPSO.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program 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 3 of the License, or
//  (at your option) any later version.
//
//  This program 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 program.  If not, see <http://www.gnu.org/licenses/>.

package jmetal.metaheuristics.omopso;

import jmetal.core.*;
import jmetal.operators.mutation.Mutation;
import jmetal.util.Distance;
import jmetal.util.JMException;
import jmetal.util.NonDominatedSolutionList;
import jmetal.util.PseudoRandom;
import jmetal.util.archive.CrowdingArchive;
import jmetal.util.comparators.CrowdingDistanceComparator;
import jmetal.util.comparators.DominanceComparator;
import jmetal.util.comparators.EpsilonDominanceComparator;

import java.util.Comparator;

/**
 * This class representing an asynchronous version of OMOPSO algorithm
 */
public class OMOPSO extends Algorithm {
                 
  /**
   * Stores the number of particles_ used
   */
  private int particlesSize_;
  
  /**
  * Stores the maximum size for the archive
  */
  private int archiveSize_;
  
  /**
  * Stores the maximum number of iteration_
  */
  private int maxIterations_;
  
  /**
  * Stores the current number of iteration_
  */
  private int iteration_;
  
  /**
  * Stores the perturbation used by the non-uniform mutation
  */
  private double perturbation_;
  
  /**
  * Stores the particles
  */
  private SolutionSet particles_;
  
  /**
   * Stores the best_ solutions founds so far for each particles
   */
  private Solution[] best_;
  
  /**
  * Stores the leaders_
  */
  private CrowdingArchive leaders_ ;
  
  /**
  * Stores the epsilon-archive
  */
  private NonDominatedSolutionList eArchive_;
  
  /**
  * Stores the speed_ of each particle
  */
  private double [][] speed_;  
  
  /**
  * Stores a comparator for checking dominance
  */
  private Comparator dominance_;
  
  /**
  * Stores a comparator for crowding checking
  */
  private Comparator crowdingDistanceComparator_;
  
  /**
   * Stores a <code>Distance</code> object
   */
  private Distance distance_;
  
  /**
  * Stores a operator for uniform mutations
  */
  private Operator uniformMutation_;
  
  /**
  * Stores a operator for non uniform mutations
  */ 
  private Operator nonUniformMutation_;
  
  /**
  * eta_ value
  */
  private double eta_ = 0.0075;

  /** 
  * Constructor
  * @param problem Problem to solve
  */    
  public OMOPSO(Problem problem) {                
    super (problem) ;
  } // OMOPSO
  
  /**
   * Initialize all parameter of the algorithm
   */
  public void initParams(){
    particlesSize_ = ((Integer)getInputParameter("swarmSize")).intValue();
    archiveSize_   = ((Integer)getInputParameter("archiveSize")).intValue();
    maxIterations_ = ((Integer)getInputParameter("maxIterations")).intValue();

    
    particles_     = new SolutionSet(particlesSize_);        
    best_          = new Solution[particlesSize_];
    leaders_       = new CrowdingArchive(archiveSize_,problem_.getNumberOfObjectives());
    eArchive_      = new NonDominatedSolutionList(new EpsilonDominanceComparator(eta_));
    
    uniformMutation_ = (Mutation)operators_.get("uniformMutation") ;
    nonUniformMutation_ = (Mutation)operators_.get("nonUniformMutation") ;
    
    // Create the dominator for equadless and dominance
    dominance_          = new DominanceComparator();    
    crowdingDistanceComparator_ = new CrowdingDistanceComparator();
    distance_           = new Distance();
    
    // Create the speed_ vector
    speed_ = new double[particlesSize_][problem_.getNumberOfVariables()];
  } // initParams
           
  
  /**
   * Update the spped of each particle
   * @throws jmetal.util.JMException
   */
  private void computeSpeed() throws JMException{        
    double r1,r2,W,C1,C2; 
    Variable[] bestGlobal;                                            
        
    for (int i = 0; i < particlesSize_; i++){
      Variable[] particle     = particles_.get(i).getDecisionVariables();
      Variable[] bestParticle = best_[i].getDecisionVariables();                        

      //Select a global best_ for calculate the speed of particle i, bestGlobal
      Solution one, two;
      int pos1 = PseudoRandom.randInt(0,leaders_.size()-1);
      int pos2 = PseudoRandom.randInt(0,leaders_.size()-1);
      one = leaders_.get(pos1);
      two = leaders_.get(pos2);

      if (crowdingDistanceComparator_.compare(one,two) < 1)
        bestGlobal = one.getDecisionVariables();
      else
        bestGlobal = two.getDecisionVariables();
      //
            
      //Params for velocity equation
      r1 = PseudoRandom.randDouble();
      r2 = PseudoRandom.randDouble();
      C1 = PseudoRandom.randDouble(1.5,2.0);
      C2 = PseudoRandom.randDouble(1.5,2.0);
      W  = PseudoRandom.randDouble(0.1,0.5);            
      //

      for (int var = 0; var < particle.length; var++){                                     
        //Computing the velocity of this particle
        speed_[i][var] = W  * speed_[i][var] +
                   C1 * r1 * (bestParticle[var].getValue() - 
                              particle[var].getValue()) +
                   C2 * r2 * (bestGlobal[var].getValue() - 
                              particle[var].getValue());
      }
                
    }
  } // computeSpeed
     
  /**
   * Update the position of each particle
   * @throws jmetal.util.JMException
   */
  private void computeNewPositions() throws JMException{
    for (int i = 0; i < particlesSize_; i++){
    	Variable[] particle = particles_.get(i).getDecisionVariables();
      //particle.move(speed_[i]);
      for (int var = 0; var < particle.length; var++){
        particle[var].setValue(particle[var].getValue()+ speed_[i][var]);
        if (particle[var].getValue() < problem_.getLowerLimit(var)){
          particle[var].setValue(problem_.getLowerLimit(var));                    
          speed_[i][var] = speed_[i][var] * -1.0;    
        }
        if (particle[var].getValue() > problem_.getUpperLimit(var)){
          particle[var].setValue(problem_.getUpperLimit(var));                    
          speed_[i][var] = speed_[i][var] * -1.0;    
        }                                             
      }
    }
  } // computeNewPositions
        
   
  /**
   * Apply a mutation operator to all particles in the swarm
   * @throws jmetal.util.JMException
   */
  private void mopsoMutation(int actualIteration, int totalIterations) throws JMException{       
    //There are three groups of particles_, the ones that are mutated with
    //a non-uniform mutation operator, the ones that are mutated with a 
    //uniform mutation and the one that no are mutated
    nonUniformMutation_.setParameter("currentIteration",actualIteration);
    //*/

    for (int i = 0; i < particles_.size();i++)            
      if (i % 3 == 0) { //particles_ mutated with a non-uniform mutation
        nonUniformMutation_.execute(particles_.get(i));                                
      } else if (i % 3 == 1) { //particles_ mutated with a uniform mutation operator
        uniformMutation_.execute(particles_.get(i));                
      } else //particles_ without mutation
          ;      
  } // mopsoMutation
   
    
  /**   
  * Runs of the OMOPSO algorithm.
  * @return a <code>SolutionSet</code> that is a set of non dominated solutions
  * as a result of the algorithm execution  
   * @throws jmetal.util.JMException
  */  
  public SolutionSet execute() throws JMException, ClassNotFoundException {
    initParams();

    //->Step 1 (and 3) Create the initial population and evaluate
    for (int i = 0; i < particlesSize_; i++){
      Solution particle = new Solution(problem_);
      problem_.evaluate(particle);
      problem_.evaluateConstraints(particle);
      particles_.add(particle);                   
    }
        
    //-> Step2. Initialize the speed_ of each particle to 0
    for (int i = 0; i < particlesSize_; i++) {
      for (int j = 0; j < problem_.getNumberOfVariables(); j++) {
        speed_[i][j] = 0.0;
      }
    }
    
        
    // Step4 and 5   
    for (int i = 0; i < particles_.size(); i++){
      Solution particle = new Solution(particles_.get(i));            
      if (leaders_.add(particle)){
        eArchive_.add(new Solution(particle));
      }
    }
                
    //-> Step 6. Initialice the memory of each particle
    for (int i = 0; i < particles_.size(); i++){
      Solution particle = new Solution(particles_.get(i));           
      best_[i] = particle;
    }
        
    //Crowding the leaders_
    distance_.crowdingDistanceAssignment(leaders_,problem_.getNumberOfObjectives());        

    //-> Step 7. Iterations ..        
    while (iteration_ < maxIterations_){
      //Compute the speed_        
      computeSpeed();
            
      //Compute the new positions for the particles_            
      computeNewPositions();

      //Mutate the particles_          
      mopsoMutation(iteration_,maxIterations_);                       
            
      //Evaluate the new particles_ in new positions
      for (int i = 0; i < particles_.size(); i++){
        Solution particle = particles_.get(i);
        problem_.evaluate(particle);                
        problem_.evaluateConstraints(particle);                
      }
            
      //Actualize the archive          
      for (int i = 0; i < particles_.size(); i++){
        Solution particle = new Solution(particles_.get(i));                
        if (leaders_.add(particle)){
          eArchive_.add(new Solution(particle));
        }                
      }
            
      //Actualize the memory of this particle
      for (int i = 0; i < particles_.size();i++){
        int flag = dominance_.compare(particles_.get(i),best_[i]);
        if (flag != 1) { // the new particle is best_ than the older remeber        
          Solution particle = new Solution(particles_.get(i));                    
          //this.best_.reemplace(i,particle);
          best_[i] = particle;
        }
      }       
            
      //Crowding the leaders_
      distance_.crowdingDistanceAssignment(leaders_,
                                              problem_.getNumberOfObjectives());            
      iteration_++;
    }
        
    return this.leaders_;
    //return eArchive_;
  } // execute
    
  /** 
   * Gets the leaders of the OMOPSO algorithm
   */
  public SolutionSet getLeader(){
    return leaders_;
  }  // getLeader 
} // OMOPSO