com.ciphertool.genetics.Population.java Source code

Java tutorial

Introduction

Here is the source code for com.ciphertool.genetics.Population.java

Source

/**
 * Copyright 2012 George Belden
 * 
 * This file is part of ZodiacGenetics.
 * 
 * ZodiacGenetics 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 3 of the License, or (at your option) any
 * later version.
 * 
 * ZodiacGenetics 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.
 * 
 * You should have received a copy of the GNU General Public License along with
 * ZodiacGenetics. If not, see <http://www.gnu.org/licenses/>.
 */

package com.ciphertool.genetics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.task.TaskExecutor;

import com.ciphertool.genetics.algorithms.selection.modes.Selector;
import com.ciphertool.genetics.entities.Chromosome;
import com.ciphertool.genetics.entities.GenerationStatistics;
import com.ciphertool.genetics.util.Breeder;
import com.ciphertool.genetics.util.FitnessComparator;
import com.ciphertool.genetics.util.FitnessEvaluator;

public class Population {
    private Logger log = Logger.getLogger(getClass());
    private Breeder breeder;
    private List<Chromosome> individuals = new ArrayList<Chromosome>();
    private List<Chromosome> ineligibleForReproduction = new ArrayList<Chromosome>();
    private FitnessEvaluator fitnessEvaluator;
    private FitnessComparator fitnessComparator;
    private Selector selector;
    private Double totalFitness = 0.0;
    private TaskExecutor taskExecutor;
    private int lifespan;
    private FitnessEvaluator knownSolutionFitnessEvaluator;
    private static final boolean COMPARE_TO_KNOWN_SOLUTION_DEFAULT = false;
    private Boolean compareToKnownSolution = COMPARE_TO_KNOWN_SOLUTION_DEFAULT;

    public Population() {
    }

    /**
     * A concurrent task for adding a brand new Chromosome to the population.
     */
    private class GeneratorTask implements Callable<Chromosome> {

        public GeneratorTask() {
        }

        @Override
        public Chromosome call() throws Exception {
            return breeder.breed();
        }
    }

    public int breed(Integer maxIndividuals) {
        List<FutureTask<Chromosome>> futureTasks = new ArrayList<FutureTask<Chromosome>>();
        FutureTask<Chromosome> futureTask = null;

        int individualsAdded = 0;
        for (int i = this.individuals.size(); i < maxIndividuals; i++) {
            futureTask = new FutureTask<Chromosome>(new GeneratorTask());
            futureTasks.add(futureTask);

            this.taskExecutor.execute(futureTask);
        }

        for (FutureTask<Chromosome> future : futureTasks) {
            try {
                this.individuals.add(future.get());

                individualsAdded++;
            } catch (InterruptedException ie) {
                log.error("Caught InterruptedException while waiting for GeneratorTask ", ie);
            } catch (ExecutionException ee) {
                log.error("Caught ExecutionException while waiting for GeneratorTask ", ee);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("Added " + individualsAdded + " individuals to the population.");
        }

        return individualsAdded;
    }

    /**
     * A concurrent task for evaluating the fitness of a Chromosome.
     */
    private class EvaluatorTask implements Callable<Double> {

        private Chromosome chromosome;

        public EvaluatorTask(Chromosome chromosome) {
            this.chromosome = chromosome;
        }

        @Override
        public Double call() throws Exception {
            return fitnessEvaluator.evaluate(this.chromosome);
        }
    }

    /**
     * This method executes all the fitness evaluations concurrently.
     */
    private void doConcurrentFitnessEvaluations() {
        List<FutureTask<Double>> futureTasks = new ArrayList<FutureTask<Double>>();
        FutureTask<Double> futureTask = null;

        int evaluationCount = 0;
        for (Chromosome individual : individuals) {
            /*
             * Only evaluate individuals that have changed since the last
             * evaluation.
             */
            if (individual.isDirty()) {
                evaluationCount++;
                futureTask = new FutureTask<Double>(new EvaluatorTask(individual));
                futureTasks.add(futureTask);
                this.taskExecutor.execute(futureTask);
            }
        }
        log.debug("Evaluations carried out: " + evaluationCount);

        for (FutureTask<Double> future : futureTasks) {
            try {
                future.get();
            } catch (InterruptedException ie) {
                log.error("Caught InterruptedException while waiting for EvaluatorTask ", ie);
            } catch (ExecutionException ee) {
                log.error("Caught ExecutionException while waiting for EvaluatorTask ", ee);
            }
        }
    }

    public Chromosome evaluateFitness(GenerationStatistics generationStatistics) {
        this.doConcurrentFitnessEvaluations();

        this.totalFitness = 0.0;

        Chromosome bestFitIndividual = null;

        for (Chromosome individual : individuals) {
            this.totalFitness += individual.getFitness();

            if (bestFitIndividual == null || individual.getFitness() > bestFitIndividual.getFitness()) {
                bestFitIndividual = individual;
            }
        }

        Double averageFitness = Double.valueOf(this.totalFitness) / Double.valueOf(individuals.size());

        if (log.isDebugEnabled()) {
            log.debug("Population of size " + individuals.size() + " has an average fitness of "
                    + String.format("%1$,.2f", averageFitness));

            log.debug("Best fitness in population is " + String.format("%1$,.2f", bestFitIndividual.getFitness()));
        }

        if (generationStatistics != null) {
            generationStatistics.setAverageFitness(averageFitness);
            generationStatistics.setBestFitness(bestFitIndividual.getFitness());

            if (this.compareToKnownSolution) {
                /*
                 * We have to clone the best fit individual since the
                 * knownSolutionFitnessEvaluator sets properties on the
                 * Chromosome, and we want it to do that in all other cases.
                 */
                Chromosome bestFitClone = bestFitIndividual.clone();
                generationStatistics
                        .setKnownSolutionProximity(this.knownSolutionFitnessEvaluator.evaluate(bestFitClone));
            }
        }

        return bestFitIndividual;
    }

    public int increaseAge() {
        int originalSize = this.individuals.size();
        Chromosome individual = null;
        int individualsRemoved = 0;

        /*
         * actualIndex is used for removing individuals from this Population
         * since the size will decrement each time an individual is removed,
         * thus making the loop index incorrect.
         */
        int actualIndex = 0;
        for (int i = 0; i < originalSize; i++) {
            individual = this.individuals.get(actualIndex);

            /*
             * A value less than zero represents immortality, so always increase
             * the age in that case. Otherwise, only increase the age if this
             * individual has more generations to live.
             */
            if (this.lifespan < 0 || individual.getAge() < this.lifespan) {
                individual.increaseAge();
                actualIndex++;
            } else {
                /*
                 * We have to remove by index in case there is more than one
                 * Chromosome that is equal, since more than likely the unique
                 * key will not have been generated from database yet.
                 */
                this.removeIndividual(actualIndex);
                individualsRemoved++;
            }
        }

        return individualsRemoved;
    }

    /*
     * This method depends on the totalFitness and individuals' fitness being
     * accurately maintained. Returns the index of the Chromosome chosen.
     */
    public int selectIndex() {
        return this.selector.getNextIndex(individuals, totalFitness);
    }

    /**
     * @return the individuals
     */
    public List<Chromosome> getIndividuals() {
        return Collections.unmodifiableList(individuals);
    }

    /**
     * Removes an individual from the population based on its index. This is
     * much more efficient than removing by equality.
     * 
     * @param individual
     */
    public Chromosome removeIndividual(int indexToRemove) {
        if (indexToRemove < 0 || indexToRemove > this.individuals.size() - 1) {
            log.error("Tried to remove individual by invalid index " + indexToRemove + " from population of size "
                    + this.size() + ".  Returning.");

            return null;
        }

        this.totalFitness -= this.individuals.get(indexToRemove).getFitness();

        return this.individuals.remove(indexToRemove);
    }

    public void clearIndividuals() {
        this.individuals.clear();

        this.totalFitness = 0.0;
    }

    /**
     * @param individual
     */
    public void addIndividual(Chromosome individual) {
        this.individuals.add(individual);

        /*
         * Only evaluate this individual if it hasn't been evaluated yet by some
         * other process.
         */
        if (individual.isDirty()) {
            fitnessEvaluator.evaluate(individual);
        }

        this.totalFitness += individual.getFitness();
    }

    /**
     * @param individual
     */
    public void addIndividualAsIneligible(Chromosome individual) {
        this.ineligibleForReproduction.add(individual);
    }

    /**
     * @param index
     */
    public void makeIneligibleForReproduction(int index) {
        this.ineligibleForReproduction.add(this.removeIndividual(index));
    }

    /**
     * Resets eligibility for all individuals which are currently ineligible for
     * reproduction.
     */
    public void resetEligibility() {
        for (Chromosome ineligibleIndividual : this.ineligibleForReproduction) {
            this.addIndividual(ineligibleIndividual);
        }

        this.ineligibleForReproduction.clear();
    }

    public int size() {
        return this.individuals.size();
    }

    public void sortIndividuals() {
        Collections.sort(individuals, this.fitnessComparator);
    }

    /**
     * Prints every Chromosome in this population in ascending order by fitness.
     * Note that a lower fitness value can be a better value depending on the
     * strategy.
     */
    public void printAscending() {
        this.sortIndividuals();

        int fitnessIndex = this.size();
        for (Chromosome individual : this.individuals) {
            log.info("Chromosome " + fitnessIndex + ": " + individual);
            fitnessIndex--;
        }
    }

    /**
     * @return the totalFitness
     */
    public Double getTotalFitness() {
        return totalFitness;
    }

    /**
     * @param obj
     *            the Object to set
     */
    public void setGeneticStructure(Object obj) {
        this.breeder.setGeneticStructure(obj);
    }

    /**
     * @param breeder
     *            the breeder to set
     */
    public void setBreeder(Breeder breeder) {
        this.breeder = breeder;
    }

    /**
     * @param fitnessEvaluator
     *            the fitnessEvaluator to set
     */
    @Required
    public void setFitnessEvaluator(FitnessEvaluator fitnessEvaluator) {
        this.fitnessEvaluator = fitnessEvaluator;
    }

    /**
     * @param fitnessComparator
     *            the fitnessComparator to set
     */
    @Required
    public void setFitnessComparator(FitnessComparator fitnessComparator) {
        this.fitnessComparator = fitnessComparator;
    }

    /**
     * @param taskExecutor
     *            the taskExecutor to set
     */
    @Required
    public void setTaskExecutor(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    /**
     * @param selector
     *            the selector to set
     */
    @Required
    public void setSelector(Selector selector) {
        this.selector = selector;
    }

    /**
     * @param lifespan
     *            the lifespan to set
     */
    @Required
    public void setLifespan(int lifespan) {
        this.lifespan = lifespan;
    }

    /**
     * This is NOT required. We will not always know the solution. In fact, that
     * should be the rare case.
     * 
     * @param knownSolutionFitnessEvaluator
     *            the knownSolutionFitnessEvaluator to set
     */
    public void setKnownSolutionFitnessEvaluator(FitnessEvaluator knownSolutionFitnessEvaluator) {
        this.knownSolutionFitnessEvaluator = knownSolutionFitnessEvaluator;
    }

    /**
     * This is NOT required.
     * 
     * @param compareToKnownSolution
     *            the compareToKnownSolution to set
     */
    public void setCompareToKnownSolution(Boolean compareToKnownSolution) {
        this.compareToKnownSolution = compareToKnownSolution;
    }
}