package ac.essex.ooechs.lcs;

import ac.essex.ooechs.lcs.Classifier;
import ac.essex.ooechs.lcs.representation.Condition;
import ac.essex.ooechs.lcs.LCS;
import ac.essex.ooechs.lcs.ClassifierSet;

/**
 * The discovery component in XCS - a simple Genetic Algorithm which
 * runs crossover and mutation operations on rules in a match set.
 * <p/>
 * <p/>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version,
 * provided that any use properly credits the author.
 * 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 General Public License for more details at http://www.gnu.org
 * </p>
 *
 * @author Olly Oechsle, University of Essex, Date: 15-Jan-2008
 * @version 1.0
 */
public class GeneticAlgorithm {

    public static double pCrossover = 0.5;
    public static double pMutation = 0.002;

    public static double predictionErrorReduction = 1.0;
    public static double fitnessReduction = 0.1;

    /**
     * Selects two parents, performs crossover and mutation on them and adds the children
     * to the rule set.
     */
    public static void run(LCS lcs, ClassifierSet set, int currentTime) {


        // choose two parents
        Classifier parent1 = tournamentSelect(set);
        Classifier parent2 = tournamentSelect(set);

        Condition newConditions[];

        if (Math.random() < pCrossover) {

            // create new conditions using crossover
            newConditions = parent1.condition.crossover(parent2.condition);

        } else {

            // no crossover
            newConditions = new Condition[2];
            newConditions[0] = parent1.condition.copy();
            newConditions[1] = parent2.condition.copy();

        }

        // perform mutation on the conditions according to pMutation probability
        newConditions[0].mutate(pMutation);
        newConditions[1].mutate(pMutation);

        // create two new rules
        Classifier child1 = new Classifier(newConditions[0], parent1.action, currentTime);
        Classifier child2 = new Classifier(newConditions[1], parent2.action, currentTime);

        // average the prediction error
        double e = predictionErrorReduction * (parent1.e + parent2.e) / 2;
        child1.e = e;
        child2.e = e;

        // average the fitness
        double fitness = fitnessReduction * (parent1.fitness + parent2.fitness) / 2;
        child1.fitness = fitness;
        child2.fitness = fitness;

        // addClassifier to the population
        lcs.addClassifier(child1);
        lcs.addClassifier(child2);

        //  reset the clocks on the match set
        set.setTimeStamp(currentTime);

    }

    /**
     * Selects a rule from the match set using tournament selection
     * with tournament size t=2
     */
    protected static Classifier tournamentSelect(ClassifierSet m) {

        // tournament size is 2
        Classifier competitor1 = m.getRandomRule();
        Classifier competitor2 = m.getRandomRule();

        if (competitor1.fitness > competitor2.fitness) return competitor1;
        else return competitor1;

    }

}
