/*
 * Selection.cpp
 *
 *  Created on: Jul 27, 2015
 *      Author: jjpalacios
 */

#include "./Selection.h"

namespace clbDEB {

//=============================================================================
//
//	Abstract class Selection
//
//=============================================================================
//=============================================================================
//		CONSTRUCTORS / INITIALIZERS
//=============================================================================
//-----  Main constructor  ----------------------------------------------------
Selection::Selection(ParameterDB * parameters) {
	if(parameters != NULL)
		this->setup(parameters);
}


//=============================================================================
//		METHODS
//=============================================================================
//-----  apply (Population)  --------------------------------------------------
Population * Selection::apply(Population *population,
		const SharedVars *svars) const {
	Population * newPopulation = new Population();
	Individual * selected;
	for(int i=0; i < population->size(); i++) {
		selected = this->select(population,svars);
		newPopulation->addIndividual(selected->clone());
	}
	return newPopulation;
}





//=============================================================================
//
//	Class TournamentSelection
//
//=============================================================================
//=============================================================================
//		CONSTRUCTORS / INITIALIZERS
//=============================================================================
//-----  Main constructor  ----------------------------------------------------
TournamentSelection::TournamentSelection(ParameterDB * parameters)
: Selection() {
	this->P_SIZE = SELECTION_SIZE;
	this->size = -1;
	if(parameters != NULL)
		this->setup(parameters);
}

//-----  Setup method  --------------------------------------------------------
void TournamentSelection::setup(ParameterDB * parameters) {
	// Load the parameters of your parent
	Selection::setup(parameters);

	// Load own parameters
	this->size = parameters->getInteger(this->P_SIZE);
	if(this->size < 0) {
		std::string err = "Invalid tournament size.";
		err += " Incorrect value or missing parameter.";
		throw ClbException(err);
	}
}



//=============================================================================
//		METHODS
//=============================================================================
//-----  select  --------------------------------------------------------------
Individual * TournamentSelection::select(Population *population,
		const SharedVars *svars) const {

	int bestPosition, randomPosition;
	Individual *selected;
	double bestFitness, currentFitness;

	// Take an individual at random
	bestPosition = svars->rng->getInteger(0, population->size()-1);
	selected = population->individual(bestPosition);
	bestFitness = selected->fitness;

	// Perform the tournament
	for(int i=0; i < this->size; i++) {
		// Ensure that we choose a different individual for the tournament
		randomPosition = svars->rng->getInteger(0, population->size()-1);
		while(randomPosition == bestPosition)
			randomPosition = svars->rng->getInteger(0, population->size()-1);
		selected = population->individual(randomPosition);
		currentFitness = selected->fitness;

		// Compare with the current champion
		if(svars->hasBetterFitness(currentFitness, bestFitness)) {
			bestFitness = currentFitness;
			bestPosition = randomPosition;
		}
	}

	return population->individual(bestPosition);
}





//=============================================================================
//
//	Class RouletteSelection
//
//=============================================================================
//=============================================================================
//		METHODS
//=============================================================================
//-----  select  --------------------------------------------------------------
Individual * RouletteSelection::select(Population *population,
		const SharedVars *svars) const {

	long int n = population->size();
	long int sumValues = (n*(n + 1)) / 2;	// God bless Gauss

	// Generates a random value
	int randomValue = svars->rng->getInteger(0, (int)sumValues-1);

	// Looks for the individual to pick
	int acum = n;
	int cont = 0;
	while(acum < randomValue) {
		cont++;
		acum += (n - cont);
	}

	return population->getBest(cont, svars);
}





//=============================================================================
//
//	Class SUSSelection
//
//=============================================================================
//=============================================================================
//		METHODS
//=============================================================================
//-----  select  --------------------------------------------------------------
Individual * SUSSelection::select(Population *population,
		const SharedVars *svars) const {

	long int n = population->size();
	long int sumValues = (n*(n + 1)) / 2;

	// Generates a random value
	int randomValue = svars->rng->getInteger(0, (int)sumValues-1);

	/* Looks for the individual to pick
	 * Because we are using linear interpolation, the Kth best individual has
	 * ((n-K+1) / sumValues) probability of being selected.
	 */
	int acum = n;
	int cont = 0;
	while(acum < randomValue) {
		cont++;
		acum += (n - cont);
	}

	return population->getBest(cont, svars);
}



//-----  apply  --------------------------------------------------------------
Population * SUSSelection::apply(Population *population,
		const SharedVars *svars) const {
	long int n = population->size();

	/* Because we are using linear interpolation, the ith worst individual has
	 * (i / N) probability of being selected, where N=(n*(n + 1)) / 2.
	 * This is equivalent to ( (2*i) / (n*(n+1)) ).
	 * Due to the expected value being Prob * n, if we use the previous
	 * expression we obtain expected value = ( (2*i) / (n+1) )
	 */
	std::vector<double> expected(n);
	for(int i=1; i <= population->size(); i++) {
		expected[population->whoIsBest(n-i, svars)] = (2.0*i) / (n+1);
	}

	Population * newPopulation = new Population();
	std::vector<Individual *> selectedIndividuals;

	// Residual value
	double ptr = svars->rng->getProbability();

	// SUS algorithm
	double sum = 0.0;
	for(int i=0; i < n; i++) {
		sum += expected[i];
		while(sum > ptr) {
			selectedIndividuals.push_back(population->individual(i));
			ptr += 1.0;
		}
	}
	svars->rng->shuffle(selectedIndividuals);
	for(int i=0;i < (int)selectedIndividuals.size(); i++)
		newPopulation->addIndividual(selectedIndividuals[i]->clone());
	return newPopulation;
}





//=============================================================================
//
//	Class ShuffleSelection
//
//=============================================================================
//=============================================================================
//		METHODS
//=============================================================================
//-----  select  --------------------------------------------------------------
Individual * ShuffleSelection::select(Population *population,
		const SharedVars *svars) const {

	int randomValue = svars->rng->getInteger(0, population->size()-1);

	return population->individual(randomValue);
}



//-----  apply  --------------------------------------------------------------
Population * ShuffleSelection::apply(Population *population,
		const SharedVars *svars) const {
	std::vector<int> randomOrder =
			svars->rng->getRandomVector(0, population->size()-1);

	Population * newPopulation = new Population();
	Individual * selected;
	for(int i=0; i < population->size(); i++) {
		selected = population->individual(randomOrder[i]);
		newPopulation->addIndividual(selected->clone());
	}

	return newPopulation;
}





//=============================================================================
//
//	Class RandomSelection
//
//=============================================================================
//=============================================================================
//		METHODS
//=============================================================================
//-----  select  --------------------------------------------------------------
Individual * RandomSelection::select(Population *population,
		const SharedVars *svars) const {

	int randomValue = svars->rng->getInteger(0, population->size()-1);
	return population->individual(randomValue);
}


}
