package ac.essex.gp.problems.examples.coevolve;

import ac.essex.gp.problems.CoevolutionProblem;
import ac.essex.gp.problems.Problem;
import ac.essex.gp.problems.DataStack;
import ac.essex.gp.Evolve;
import ac.essex.gp.individuals.Individual;
import ac.essex.gp.nodes.*;
import ac.essex.gp.params.GPParams;
import ac.essex.gp.params.NodeConstraints;
import ac.essex.gp.interfaces.graphical.GraphicalListener;
import ac.essex.gp.interfaces.console.ConsoleListener;

/**
 * <p>
 * Coevolution is useful when a certain piece of logic tends to be used over and over again,
 * either within one individual, or across the population as a whole. As we don't want to
 * waste time creating this logic over and over again, co-evolution helps to protect these
 * useful little functions.
 * </p>
 * <p>
 * Coevolution problems require only minor changes in your code. The initialise and customise parameter
 * methods have an additional parameter for the co-evolution params which controls the population of
 * co-evolved features. The co-evolved params object is already initialised with sensible parameter values.
 * You need to do two sets of node registration - one for the main parameters and once for the co-evolution
 * parameters, these may be different. Often you may want to give the image processing operators to the
 * co-evolution params and use the regular parameters to combine these in some way with mathematical
 * or boolean functions. See the in the code below for details.
 * </p>
 * @author Olly Oechsle, University of Essex, Date: 30-Jan-2008
 * @version 1.0
 */
public class RegularMathsProblem extends Problem {

    public static void main(String[] args) {
        Problem p = new RegularMathsProblem();
        Evolve.seed = 1233;
        Evolve e = new Evolve(p, new ConsoleListener());
        e.run();
    }

    /**
     * Provides a name for the problem so that it can be identified via the
     * user interface.
     */
    public String getName() {
        return "Regular Maths Problem";
    }

    /**
     * Initialises the problem. This is where the training data is loaded
     * and the GP params object initialised with Nodes to use. The return
     * object should also be set up.
     */
    public void initialise(Evolve e, GPParams params) {

        // Set up the nodes
        params.setReturnType(NodeConstraints.NUMBER);
        params.registerNode(new Add());
        params.registerNode(new Sub());
        params.registerNode(new Mul());
        params.registerNode(new Div());
        params.registerNode(new X());
        params.setPopulationSize(200);
    }

    /**
     * Where the problem can customise its GP parameters.
     */
    public void customiseParameters(GPParams params) {
        params.setMinTreeDepth(2);
        params.setMaxTreeDepth(5);
    }

    /**
     * Evaluates a single individual, fitness should be assigned using the
     * setKozaFitness() method on the individual.
     */
    public void evaluate(Individual ind, DataStack data, Evolve e) {

        double fitness = 0;

        int hits = 0;

        // try a few different values of X
        for (int x = 2; x < 10; x++) {

            // maths problem - the function we expect
            double expected = (x * x * x * x * x) - (2 * (x * x * x)) + x;

            // put this into the data stack
            data.setX(x);

            // see what we get back
            double received = ind.execute(data);

            // count the number of hits
            if (received == expected) hits++;

            // regression - find the difference between expected and received
            fitness += Math.abs(expected - received);

        }

        ind.setKozaFitness(fitness);
        ind.setHits(hits);

    }

}
