/*
 * Encog(tm) Core v3.2 - Java Version
 * http://www.heatonresearch.com/encog/
 * https://github.com/encog/encog-java-core
 
 * Copyright 2008-2013 Heaton Research, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *   
 * For more information on Heaton Research copyrights, licenses 
 * and trademarks visit:
 * http://www.heatonresearch.com/copyright
 */
package org.encog.neural.neat;

import java.io.Serializable;
import java.util.Random;

import org.encog.Encog;
import org.encog.engine.network.activation.ActivationFunction;
import org.encog.engine.network.activation.ActivationSteepenedSigmoid;
import org.encog.mathutil.randomize.factory.RandomFactory;
import org.encog.ml.MLError;
import org.encog.ml.MLRegression;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.ea.codec.GeneticCODEC;
import org.encog.ml.ea.genome.Genome;
import org.encog.ml.ea.population.BasicPopulation;
import org.encog.ml.ea.species.BasicSpecies;
import org.encog.neural.NeuralNetworkError;
import org.encog.neural.hyperneat.FactorHyperNEATGenome;
import org.encog.neural.hyperneat.HyperNEATCODEC;
import org.encog.neural.hyperneat.HyperNEATGenome;
import org.encog.neural.hyperneat.substrate.Substrate;
import org.encog.neural.neat.training.NEATGenome;
import org.encog.neural.neat.training.NEATInnovationList;
import org.encog.util.identity.BasicGenerateID;
import org.encog.util.identity.GenerateID;
import org.encog.util.obj.ChooseObject;

/**
 * A population for a NEAT or HyperNEAT system. This population holds the
 * genomes, substrate and other values for a NEAT or HyperNEAT network.
 * -----------------------------------------------------------------------------
 * http://www.cs.ucf.edu/~kstanley/ Encog's NEAT implementation was drawn from
 * the following three Journal Articles. For more complete BibTeX sources, see
 * NEATNetwork.java.
 * 
 * Evolving Neural Networks Through Augmenting Topologies
 * 
 * Generating Large-Scale Neural Networks Through Discovering Geometric
 * Regularities
 * 
 * Automatic feature selection in neuroevolution
 */
public class NEATPopulation extends BasicPopulation implements Serializable,
		MLError, MLRegression {

	/**
	 * The default survival rate.
	 */
	public static final double DEFAULT_SURVIVAL_RATE = 0.2;

	/**
	 * The activation function to use.
	 */
	public static final String PROPERTY_NEAT_ACTIVATION = "neatAct";

	/**
	 * Property tag for the population size.
	 */
	public static final String PROPERTY_POPULATION_SIZE = "populationSize";

	/**
	 * Property tag for the survival rate.
	 */
	public static final String PROPERTY_SURVIVAL_RATE = "survivalRate";

	/**
	 * Serial id.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Default number of activation cycles.
	 */
	public static final int DEFAULT_CYCLES = 4;

	/**
	 * Property to hold the number of cycles.
	 */
	public static final String PROPERTY_CYCLES = "cycles";

	/**
	 * Change the weight, do not allow the weight to go out of the weight range.
	 * 
	 * @param w
	 *            The amount to change the weight by.
	 * @param weightRange
	 *            Specify the weight range. The range is from -weightRange to
	 *            +weightRange.
	 * @return The new weight value.
	 */
	public static double clampWeight(final double w, final double weightRange) {
		if (w < -weightRange) {
			return -weightRange;
		} else if (w > weightRange) {
			return weightRange;
		} else {
			return w;
		}
	}

	/**
	 * The number of activation cycles that the networks produced by this
	 * population will use.
	 */
	private int activationCycles = NEATPopulation.DEFAULT_CYCLES;

	/**
	 * Generate gene id's.
	 */
	private final GenerateID geneIDGenerate = new BasicGenerateID();

	/**
	 * Generate innovation id's.
	 */
	private final GenerateID innovationIDGenerate = new BasicGenerateID();

	/**
	 * A list of innovations, or null if this feature is not being used.
	 */
	private NEATInnovationList innovations;

	/**
	 * The weight range. Weights will be between -weight and +weight.
	 */
	private final double weightRange = 5;

	/**
	 * The best genome that we've currently decoded into the bestNetwork
	 * property. If this value changes to point to a new genome reference then
	 * the phenome will need to be recalculated.
	 */
	private Genome cachedBestGenome;

	/**
	 * The best network. If the population is used as an MLMethod, then this
	 * network will represent.
	 */
	private NEATNetwork bestNetwork;

	/**
	 * The number of input units. All members of the population must agree with
	 * this number.
	 */
	int inputCount;

	/**
	 * The number of output units. All members of the population must agree with
	 * this number.
	 */
	int outputCount;

	/**
	 * The survival rate.
	 */
	private double survivalRate = NEATPopulation.DEFAULT_SURVIVAL_RATE;

	/**
	 * The substrate, if this is a hyperneat network.
	 */
	private Substrate substrate;

	/**
	 * The activation functions that we can choose from.
	 */
	private final ChooseObject<ActivationFunction> activationFunctions = new ChooseObject<ActivationFunction>();

	/**
	 * The CODEC used to decode the NEAT genomes into networks. Different
	 * CODEC's are used for NEAT vs HyperNEAT.
	 */
	private GeneticCODEC codec;

	/**
	 * The initial connection density for the initial random population of
	 * genomes.
	 */
	private double initialConnectionDensity = 0.1;

	/**
	 * A factory to create random number generators.
	 */
	private RandomFactory randomNumberFactory = Encog.getInstance()
			.getRandomFactory().factorFactory();

	/**
	 * An empty constructor for serialization.
	 */
	public NEATPopulation() {

	}

	/**
	 * Construct a starting NEAT population. This does not generate the initial
	 * random population of genomes.
	 * 
	 * @param inputCount
	 *            The input neuron count.
	 * @param outputCount
	 *            The output neuron count.
	 * @param populationSize
	 *            The population size.
	 */
	public NEATPopulation(final int inputCount, final int outputCount,
			final int populationSize) {
		super(populationSize, null);
		this.inputCount = inputCount;
		this.outputCount = outputCount;

		setNEATActivationFunction(new ActivationSteepenedSigmoid());

		if (populationSize == 0) {
			throw new NeuralNetworkError(
					"Population must have more than zero genomes.");
		}

	}

	/**
	 * Construct a starting HyperNEAT population. This does not generate the
	 * initial random population of genomes.
	 * 
	 * @param theSubstrate
	 *            The substrate ID.
	 * @param populationSize
	 */
	public NEATPopulation(final Substrate theSubstrate, final int populationSize) {
		super(populationSize, new FactorHyperNEATGenome());
		this.substrate = theSubstrate;
		this.inputCount = 6;
		this.outputCount = 2;
		HyperNEATGenome.buildCPPNActivationFunctions(this.activationFunctions);
	}

	/**
	 * @return A newly generated gene id.
	 */
	public long assignGeneID() {
		return this.geneIDGenerate.generate();
	}

	/**
	 * @return A newly generated innovation id.
	 */
	public long assignInnovationID() {
		return this.innovationIDGenerate.generate();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public double calculateError(final MLDataSet data) {
		updateBestNetwork();
		return this.bestNetwork.calculateError(data);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public MLData compute(final MLData input) {
		updateBestNetwork();
		return this.bestNetwork.compute(input);
	}

	/**
	 * @return Get the activation cycles.
	 */
	public int getActivationCycles() {
		return this.activationCycles;
	}

	/**
	 * @return the activationFunctions
	 */
	public ChooseObject<ActivationFunction> getActivationFunctions() {
		return this.activationFunctions;
	}

	/**
	 * @return the codec
	 */
	public GeneticCODEC getCODEC() {
		return this.codec;
	}

	/**
	 * @return the geneIDGenerate
	 */
	public GenerateID getGeneIDGenerate() {
		return this.geneIDGenerate;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public NEATGenomeFactory getGenomeFactory() {
		return (NEATGenomeFactory) super.getGenomeFactory();
	}

	/**
	 * @return the initialConnectionDensity
	 */
	public double getInitialConnectionDensity() {
		return this.initialConnectionDensity;
	}

	/**
	 * @return the innovationIDGenerate
	 */
	public GenerateID getInnovationIDGenerate() {
		return this.innovationIDGenerate;
	}

	/**
	 * @return Get the innovations.
	 */
	public NEATInnovationList getInnovations() {
		return this.innovations;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getInputCount() {
		return this.inputCount;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getOutputCount() {
		return this.outputCount;
	}

	/**
	 * @return the randomNumberFactory
	 */
	public RandomFactory getRandomNumberFactory() {
		return this.randomNumberFactory;
	}

	/**
	 * @return Returns the hyper-neat substrate.
	 */
	public Substrate getSubstrate() {
		return this.substrate;
	}

	/**
	 * @return The survival rate, this is the number of genomes used to mate.
	 */
	public double getSurvivalRate() {
		return this.survivalRate;
	}

	/**
	 * @return the weightRange
	 */
	public double getWeightRange() {
		return this.weightRange;
	}

	/**
	 * @return Returns true if this is a hyperneat population.
	 */
	public boolean isHyperNEAT() {
		return this.substrate != null;
	}

	/**
	 * Create an initial random population.
	 */
	public void reset() {
		// create the genome factory
		if (isHyperNEAT()) {
			this.codec = new HyperNEATCODEC();
			setGenomeFactory(new FactorHyperNEATGenome());
		} else {
			this.codec = new NEATCODEC();
			setGenomeFactory(new FactorNEATGenome());
		}

		// create the new genomes
		getSpecies().clear();

		// reset counters
		getGeneIDGenerate().setCurrentID(1);
		getInnovationIDGenerate().setCurrentID(1);

		final Random rnd = this.randomNumberFactory.factor();

		// create one default species
		final BasicSpecies defaultSpecies = new BasicSpecies();
		defaultSpecies.setPopulation(this);

		// create the initial population
		for (int i = 0; i < getPopulationSize(); i++) {
			final NEATGenome genome = getGenomeFactory().factor(rnd, this,
					this.inputCount, this.outputCount,
					this.initialConnectionDensity);
			defaultSpecies.add(genome);
		}
		defaultSpecies.setLeader(defaultSpecies.getMembers().get(0));
		getSpecies().add(defaultSpecies);

		// create initial innovations
		setInnovations(new NEATInnovationList(this));
	}

	/**
	 * Set the number of activation cycles to use.
	 * @param activationCycles The number of activatino cycles to use.
	 */
	public void setActivationCycles(final int activationCycles) {
		this.activationCycles = activationCycles;
	}

	/**
	 * @param codec
	 *            the codec to set
	 */
	public void setCODEC(final GeneticCODEC codec) {
		this.codec = codec;
	}

	/**
	 * @param initialConnectionDensity
	 *            the initialConnectionDensity to set
	 */
	public void setInitialConnectionDensity(
			final double initialConnectionDensity) {
		this.initialConnectionDensity = initialConnectionDensity;
	}

	/**
	 * Set the innovation list to use.
	 * @param theInnovations The innovation list to use.
	 */
	public void setInnovations(final NEATInnovationList theInnovations) {
		this.innovations = theInnovations;
	}

	/**
	 * @param inputCount
	 *            the inputCount to set
	 */
	public void setInputCount(final int inputCount) {
		this.inputCount = inputCount;
	}

	/**
	 * Specify to use a single activation function. This is typically the case
	 * for NEAT, but not for HyperNEAT.
	 * 
	 * @param af The activation function to use.
	 */
	public void setNEATActivationFunction(final ActivationFunction af) {
		this.activationFunctions.clear();
		this.activationFunctions.add(1.0, af);
		this.activationFunctions.finalizeStructure();
	}

	/**
	 * @param outputCount
	 *            the outputCount to set
	 */
	public void setOutputCount(final int outputCount) {
		this.outputCount = outputCount;
	}

	/**
	 * @param randomNumberFactory
	 *            the randomNumberFactory to set
	 */
	public void setRandomNumberFactory(final RandomFactory randomNumberFactory) {
		this.randomNumberFactory = randomNumberFactory;
	}

	/**
	 * @param substrate
	 *            the substrate to set
	 */
	public void setSubstrate(final Substrate substrate) {
		this.substrate = substrate;
	}

	/**
	 * Set the survival rate, this is the percent of the population allowed to mate.
	 * @param theSurvivalRate The survival rate.
	 */
	public void setSurvivalRate(final double theSurvivalRate) {
		this.survivalRate = theSurvivalRate;
	}

	/**
	 * See if the best genome has changed, and decode a new best network, if
	 * needed.
	 */
	private void updateBestNetwork() {
		if (getBestGenome() != this.cachedBestGenome) {
			this.cachedBestGenome = getBestGenome();
			this.bestNetwork = (NEATNetwork) getCODEC().decode(getBestGenome());
		}
	}

}
