/*
 * NicheGenetic.cpp
 *
 *  Created on: Jul 29, 2015
 *      Author: jjpalacios
 */

#include "NicheGenetic.h"

namespace clbDEB {

//=============================================================================
//
//	Class NicheGenetic
//
//=============================================================================
//=============================================================================
//		CONSTRUCTORS / INITIALIZERS
//=============================================================================
//-----  Main constructor  ----------------------------------------------------
NicheGenetic::NicheGenetic(const char * setupFile)
		: MultimodalAlgorithm(setupFile) {
	this->crossover = NULL;
	this->mutation = NULL;
	this->selection = NULL;
	this->replacement = NULL;
	this->creation = NULL;
	this->maxGenerations = -1;
	this->maxEvaluations = -1;
	this->maxRuntime = -1;
	this->populationSize = 0;
	this->typeIndividual = INTEGER_INDIVIDUAL;
	this->nicheRadius = 0;
	this->nicheCapacity = 1;
	this->niching = CLEARING;
	this->printPopulation = false;
	this->printPopGenerations = -1;

	this->generation = 0;
	this->evaluations = 0;

	this->totalRuntime = 0;
	this->creationTime = 0;
	this->selectionTime = 0;
	this->crossoverTime = 0;
	this->mutationTime = 0;
	this->replacementTime = 0;
	this->evaluationTime = 0;
	this->nichingTime = 0;
	this->bestSoFar = NULL;
}


//-----  Destructor  ----------------------------------------------------------
NicheGenetic::~NicheGenetic() {
	delete this->crossover;
	delete this->mutation;
	delete this->selection;
	delete this->replacement;
	delete this->creation;
	delete this->bestSoFar;
}


//-----  clearAll  ------------------------------------------------------------
void NicheGenetic::clearAll() {
	delete this->crossover;
	delete this->mutation;
	delete this->selection;
	delete this->replacement;
	delete this->creation;
	delete this->bestSoFar;
	this->evolutionStats.clear();
}




//=============================================================================
//		GET/SET METHODS
//=============================================================================
//-----  printSetupTree  ------------------------------------------------------
void NicheGenetic::printSetupTree(std::ofstream & output) const {
	output << "Niche Genetic Algorithm" << std::endl;

	output << ";Number of parameters to tune:;"
			<< this->sharedVariables->numElements << std::endl;

	output << ";Type of parameters:;";
		if(this->typeIndividual == INTEGER_INDIVIDUAL) output << "integer";
		else if(this->typeIndividual == DOUBLE_INDIVIDUAL) output << "real";
		else output << "unknown";
		output << std::endl;

	std::vector< std::string > names;
	names = this->evaluator->getName();
	output << ";Evaluation Function:;" << names[0] << std::endl;
	for(int i=1; i < (int)names.size(); i++)
		output << ";" + names[i] << std::endl;
	output << ";Objective:;";
	if(this->sharedVariables->maximize) output << "Maximize" << std::endl;
	else output << "minimize" << std::endl;

	output << ";Population Size:;" << this->populationSize << std::endl;

	output << ";Stopping criteria:"  << std::endl;
	output << ";;Max.Generations:;";
	if(this->maxGenerations < 0) output << "none" << std::endl;
	else output << this->maxGenerations << std::endl;

	output << ";;Max.Evaluations:;";
	if(this->maxEvaluations < 0) output << "none" << std::endl;
	else output << this->maxEvaluations << std::endl;

	output << ";;Time Limit:;";
	if(this->maxRuntime < 0) output << "no" << std::endl;
	else output << this->maxRuntime << std::endl;

	output << ";Niching:;";
	if(this->niching == CLEARING) output << "Clearing" << std::endl;
	else if(this->niching == SHARING) output << "Sharing" << std::endl;
	else output << "unknown" << std::endl;

	output << ";;Radius:;" << this->nicheRadius << std::endl;
	output << ";;Capacity:;" << this->nicheCapacity << std::endl;

	names = this->creation->getName();
	output << ";Initial Popul.:;" << names[0] << std::endl;
	for(int i=1; i < (int)names.size(); i++)
		output << ";" + names[i] << std::endl;

	names = this->selection->getName();
	output << ";Selection:;" << names[0] << std::endl;
	for(int i=1; i < (int)names.size(); i++)
		output << ";" + names[i] << std::endl;

	names = this->crossover->getName();
	output << ";Crossover:;" << names[0] << std::endl;
	for(int i=1; i < (int)names.size(); i++)
		output << ";" + names[i] << std::endl;

	names = this->mutation->getName();
	output << ";Mutation:;" << names[0] << std::endl;
	for(int i=1; i < (int)names.size(); i++)
		output << ";" + names[i] << std::endl;

	names = this->replacement->getName();
	output << ";Replacement:;" << names[0] << std::endl;
	for(int i=1; i < (int)names.size(); i++)
		output << ";" + names[i] << std::endl;
}



//-----  getStatistics  -------------------------------------------------------
std::vector< std::pair<std::string, double> > NicheGenetic::getStatistics()
const {
	std::vector< std::pair<std::string, double> > stats;
	stats.push_back(std::pair<std::string, double>
		("Number of Generations",  this->generation));
	stats.push_back(std::pair<std::string, double>
		("Theoretical Evaluations",  this->evaluations));

	stats.push_back(std::pair<std::string, double>
			("Best solution",  this->bestSoFar->fitness));
	return stats;

}



//-----  getRuntime  ----------------------------------------------------------
std::vector< std::pair<std::string, double> > NicheGenetic::getRuntime()
const {
	std::vector< std::pair<std::string, double> > times;
	double percentage, totalPerc=0.0;

	times.push_back(std::pair<std::string, double>
			("Total runtime",  (1.0*totalRuntime) / CLOCKS_PER_SEC));

	percentage = (double)creationTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Creation",  100*percentage));
	totalPerc += percentage;

	percentage = (double)selectionTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Selection",   100*percentage));
	totalPerc += percentage;

	percentage = (double)crossoverTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Crossover",   100*percentage));
	totalPerc += percentage;

	percentage = (double)mutationTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Mutation",   100*percentage));
	totalPerc += percentage;

	percentage = (double)replacementTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Replacement", 100*percentage));
	totalPerc += percentage;

	percentage = (double)evaluationTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Evaluation",   100*percentage));
	totalPerc += percentage;

	percentage = (double)nichingTime / totalRuntime;
	times.push_back(std::pair<std::string, double>
				("Niching",   100*percentage));
	totalPerc += percentage;

	times.push_back(std::pair<std::string, double>
				("Others",  100 - 100*totalPerc));
	return times;
}



//-----  getEvolution  ----------------------------------------------------------
std::vector< std::vector<double> > NicheGenetic::getEvolution(
			std::vector< std::string > &labels) const {
	labels.clear();
	labels.push_back("Generation");
	labels.push_back("Best");
	labels.push_back("Avg");
	labels.push_back("#Niches");
	return this->evolutionStats;
}





//=============================================================================
//		METHODS
//=============================================================================
//-----  prepareToRun  --------------------------------------------------------
void NicheGenetic::prepareToRun() {
	// Loads the common parameters
	MultimodalAlgorithm::prepareToRun();

	// Loads the specific parameters
	std::string value;

	if(this->ready)
		clearAll();

	// Gets the creation strategy
	value = this->sharedVariables->parameters->getString(NGA_CREATION);
	this->creation = ClassRegister::getCreationObject(value);

	// Gets the crossover operator
	value = this->sharedVariables->parameters->getString(NGA_CROSSOVER);
	this->crossover = ClassRegister::getCrossoverObject(value);

	// Gets the mutation operator
	value = this->sharedVariables->parameters->getString(NGA_MUTATION);
	this->mutation = ClassRegister::getMutationObject(value);

	// Gets the selection operator
	value = this->sharedVariables->parameters->getString(NGA_SELECTION);
	this->selection = ClassRegister::getSelectionObject(value);

	// Gets the replacement strategy
	value = this->sharedVariables->parameters->getString(NGA_REPLACE);
	this->replacement = ClassRegister::getReplacementObject(value);

	// Gets the maximum number of generations
	this->maxGenerations = this->sharedVariables->parameters->
			getInteger(NGA_GENERATIONS, -1);

	// Gets the maximum number of evaluations
	this->maxEvaluations = this->sharedVariables->parameters->
			getInteger(NGA_EVALUATIONS, -1);

	// Gets the time limit
	this->maxRuntime = this->sharedVariables->parameters->
			getDouble(NGA_TIME, -1.0);

	// Gets the population size
	this->populationSize = this->sharedVariables->parameters->
			getInteger(NGA_POP_SIZE, -1);

	// Gets the type of individuals
	value = this->sharedVariables->parameters->
		getStringUpper(NGA_INDIVIDUAL_TYPE);
	if(value.compare("INTEGER")==0) {
		this->typeIndividual = INTEGER_INDIVIDUAL;
	}
	else if(value.compare("REAL")==0)
		this->typeIndividual = DOUBLE_INDIVIDUAL;
	else {
		std::cout << "Warning: No gene type specified.";
		std::cout << " Using integer values...(continuing).";
		sleep(5);
	}

	// Gets the niching parameters
	value = this->sharedVariables->parameters->
		getStringUpper(NGA_NICHE_STRATEGY);
	if(value.compare(toUpper(NGA_NICHE_CLEARING))==0) this->niching = CLEARING;
	else if(value.compare(toUpper(NGA_NICHE_SHARING))==0) this->niching = SHARING;
	else {
		std::cout << "Warning: No niching strategy specified.";
		std::cout << " Using clearing...(continuing).";
		sleep(5);
	}

	this->nicheRadius = this->sharedVariables->parameters->
			getDouble(NGA_NICHE_RADIUS, -1.0);

	this->nicheCapacity = this->sharedVariables->parameters->
			getInteger(NGA_NICHE_CAPACITY, -1);

	// Checks if the populations must be printed
	this->printPopulation = this->sharedVariables->parameters->
			getBoolean(NGA_PRINT_POPULATION, false);
	this->printPopGenerations = this->sharedVariables->parameters->
			getInteger(NGA_POP_INTERVAL, 1);

	try {
		this->checkSetup();
	} catch(ClbException &exc) {
		throw exc;
	}

	this->crossover->setup(this->sharedVariables->parameters);
	this->mutation->setup(this->sharedVariables->parameters);
	this->selection->setup(this->sharedVariables->parameters);
	this->replacement->setup(this->sharedVariables->parameters);
}



//-----  checkSetup  ----------------------------------------------------------
bool NicheGenetic::checkSetup() {
	bool correct = true;
	std::string err = "";
	if(this->creation == NULL) {
		err = "Invalid creation strategy.";
		correct = false;
	}
	if(this->crossover == NULL) {
		err = "Invalid crossover operator.";
		correct = false;
	}
	if(this->mutation == NULL) {
		err = "Invalid mutation operator.";
		correct = false;
	}
	if(this->replacement == NULL) {
		err = "Invalid replacement operator.";
		correct = false;
	}
	if(this->selection == NULL) {
		err = "Invalid selection operator.";
		correct = false;
	}

	if(this->maxGenerations < 0 && this->maxEvaluations < 0
			&& this->maxRuntime < 0) {
		err = "No stopping criteria defined.";
		correct = false;
	}

	if(this->populationSize < 0) {
		err = "Invalid population size.";
		correct = false;
	}

	if(this->nicheRadius < 0) {
		err = "Invalid niche radius.";
		correct = false;
	}

	if(this->nicheCapacity < 0) {
		std::cout << "Niche capacity not found. Using 1 by default";
		this->nicheCapacity = 1;
	}

	if(!correct) {
		err += " Incorrect value or missing parameter";
		throw ClbException(err);
	}

	return true;
}



//-----  RUN !!  --------------------------------------------------------------
//=============================================================================
Results * NicheGenetic::run(std::string signature, std::string logFolder,
		int rngSeed) {

	Population *currentPopulation;
	std::vector<double> stats;
	clock_t timePoint;
	int nNiches;

	// Initialize dynamic variables
	this->creationTime = 0;
	this->selectionTime = 0;
	this->crossoverTime = 0;
	this->mutationTime = 0;
	this->replacementTime = 0;
	this->evaluationTime = 0;
	this->nichingTime = 0;
	evolutionStats.clear();

	this->generation = 0;
	this->evaluations = 0;

	// Initialize the RNG
	this->sharedVariables->rng->init(rngSeed);

	this->totalRuntime = clock();

	// Create initial population  -------------------------
	this->creationTime = clock();
	currentPopulation = this->creation->createPopulation(this->typeIndividual,
			this->sharedVariables->numElements,
			this->populationSize, this->sharedVariables);
	this->creationTime = clock() - this->creationTime;


	// Evaluate the initial population  -------------------
	this->evaluationTime = clock();
	this->evaluator->evaluatePopulation(this->sharedVariables, currentPopulation);
	this->evaluationTime = clock() - this->evaluationTime;

	// Statistics....
	if(this->bestSoFar != NULL)
		delete this->bestSoFar;
	this->bestSoFar = currentPopulation->
			getBest(0, this->sharedVariables)->clone();
	stats.push_back(0);	// Iteration
	stats.push_back(this->bestSoFar->fitness);	// Best solution
	stats.push_back(currentPopulation->getAverage());	// Average quality
	std::cout << "Generation 0:" << std::endl;
	std::cout << "Best fitness: " << this->bestSoFar->fitness << std::endl;
	std::cout << "Best chromosome: " << this->bestSoFar->toString() << std::endl << std::endl;


	// While not stopping criteria is met...
	Population *offspring;
	while(!this->stop()) {
		if(this->printPopulation &&
				this->generation % this->printPopGenerations == 0)
			Statistics::printPopulation(this->generation, signature, logFolder,
					currentPopulation);

		// Apply the Clearing / Sharing  ------------------
		timePoint = clock();
		if(this->niching == CLEARING)
			nNiches = this->clearing(currentPopulation);
		else if(this->niching == SHARING)
			nNiches = this->sharing(currentPopulation);
		this->nichingTime += clock() - timePoint;
		stats.push_back(nNiches);
		evolutionStats.push_back(stats);
		stats.clear();

		// Select individuals for mating  -----------------
		timePoint = clock();
		offspring = this->selection->apply(currentPopulation,
				this->sharedVariables);
		this->selectionTime += clock() - timePoint;

		// Crossover  -------------------------------------
		timePoint = clock();
		this->crossover->apply(offspring, this->sharedVariables);
		this->crossoverTime += clock() - timePoint;

		// Mutation  --------------------------------------
		timePoint = clock();
		this->mutation->apply(offspring, this->sharedVariables);
		this->mutationTime += clock() - timePoint;

		// Evaluation. Force it because fitness from previous iterations
		// may be unreal (changed by clearing/sharing)  ---
		timePoint = clock();
		this->evaluator->evaluatePopulation(this->sharedVariables,
				offspring, true);
		this->evaluations += currentPopulation->size();
		this->evaluationTime += clock() - timePoint;

		// Generational Replacement  ----------------------
		timePoint = clock();
		this->replacement->apply(currentPopulation, offspring,
				this->sharedVariables);
		currentPopulation = offspring;
		this->replacementTime += clock() - timePoint;

		this->generation++;

		// Statistics....
		delete this->bestSoFar;
		this->bestSoFar = currentPopulation->
				getBest(0, this->sharedVariables)->clone();
		stats.push_back(this->generation);	// Iteration
		stats.push_back(this->bestSoFar->fitness);	// Best solution
		stats.push_back(currentPopulation->getAverage());	// Average quality
		std::cout << "Generation " << this->generation << ":" << std::endl;
		std::cout << "Best fitness: " << this->bestSoFar->fitness << std::endl;
		std::cout << "Best chromosome: " << this->bestSoFar->toString()
				<< std::endl << std::endl;
	}

	this->totalRuntime = clock() - this->totalRuntime;
	this->finished = true;

	// Return set of best solutions  ----------------------
	if(this->niching == CLEARING) {
		Results * results = new Results();
		nNiches = this->clearing(currentPopulation);
		for(int i=0; i < currentPopulation->size(); i++) {
			if(!(this->sharedVariables->isPenalty(currentPopulation->getFitness(i))))
				results->addIndividual(
						currentPopulation->individual(i)->clone());
		}
		delete currentPopulation;
		return results;
	}

	this->evaluator->evaluatePopulation(this->sharedVariables,
			currentPopulation);
	return currentPopulation;
}



//-----  clearing  ------------------------------------------------------------
int NicheGenetic::clearing(Population *population) {
	int nbWinners, nNiches;
	Individual *ind_i, *ind_j;
	double distance;

	nNiches = 0;
	for(int i=0; i < population->size(); i++) {
		ind_i = population->getBest(i, this->sharedVariables);
		if(!(this->sharedVariables->isPenalty(ind_i->fitness))) {
			nbWinners = 1;
			nNiches++;

			// Look for individuals in the same niche
			for(int j=i+1; j < population->size(); j++) {
				ind_j = population->getBest(j, this->sharedVariables);
				if(!(this->sharedVariables->isPenalty(ind_j->fitness))) {
					if(this->typeIndividual == INTEGER_INDIVIDUAL)
						distance = euclideanDistance(
							this->sharedVariables,
							dynamic_cast<IntegerIndividual *>(ind_i),
							dynamic_cast<IntegerIndividual *>(ind_j)
						);
					else if(this->typeIndividual == DOUBLE_INDIVIDUAL)
						distance = euclideanDistance(
							this->sharedVariables,
							dynamic_cast<DoubleIndividual *>(ind_i),
							dynamic_cast<DoubleIndividual *>(ind_j)
						);
					if(distance < this->nicheRadius) {
						if(nbWinners < this->nicheCapacity)
							nbWinners++;
						else
							population->individual(
								population->whoIsBest(j, this->sharedVariables))
								->fitness = this->sharedVariables->penaltyFitness();
					}
				}
			}
		}
	}
	population->sort(this->sharedVariables, true);
	return nNiches;
}



//-----  sharing  -------------------------------------------------------------
int NicheGenetic::sharing(Population *population) {
	Individual *ind_i, *ind_j;
	double distance, share;
	std::vector<double> shareFunction(population->size(), 1.0);

	for(int i=0; i < population->size(); i++) {
		ind_i = population->individual(i);

		for(int j=i+1; j < population->size(); j++) {
			ind_j = population->individual(j);

			// Compute distance
			if(this->typeIndividual == INTEGER_INDIVIDUAL)
				distance = euclideanDistance(
					this->sharedVariables,
					dynamic_cast<IntegerIndividual *>(ind_i),
					dynamic_cast<IntegerIndividual *>(ind_j)
				);
			else if(this->typeIndividual == DOUBLE_INDIVIDUAL)
				distance = euclideanDistance(
					this->sharedVariables,
					dynamic_cast<DoubleIndividual *>(ind_i),
					dynamic_cast<DoubleIndividual *>(ind_j)
				);

			// Compute sharing function
			if(distance <  this->nicheRadius) {
				share = 1 - (distance / this->nicheRadius);
				shareFunction[i] += share;
				shareFunction[j] += share;
			}
		}
		if(this->sharedVariables->maximize)
			population->setFitness(i,
					population->getFitness(i) / shareFunction[i]);
		else
			population->setFitness(i,
					population->getFitness(i) * shareFunction[i]);
	}
	return 0;
}



//-----  stop  ----------------------------------------------------------------
bool NicheGenetic::stop() {
	if(this->generation >= this->maxGenerations && this->maxGenerations >= 0)
		return true;
	if(this->evaluations >= this->maxEvaluations && this->maxEvaluations >= 0)
		return true;
	clock_t clockTime = clock() - this->totalRuntime;
	double currentRuntime = clockTime / (double)CLOCKS_PER_SEC;
	if(this->maxRuntime >= 0 && currentRuntime >= this->maxRuntime)
		return true;
	return false;
}


}

