com.ciphertool.genetics.algorithms.mutation.GroupMutationAlgorithm.java Source code

Java tutorial

Introduction

Here is the source code for com.ciphertool.genetics.algorithms.mutation.GroupMutationAlgorithm.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.algorithms.mutation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import com.ciphertool.genetics.dao.GeneListDao;
import com.ciphertool.genetics.entities.Chromosome;
import com.ciphertool.genetics.entities.Gene;

public class GroupMutationAlgorithm implements MutationAlgorithm {
    private static Logger log = Logger.getLogger(GroupMutationAlgorithm.class);

    private static final int MAX_GENES_PER_GROUP = 5;

    /*
     * This is set to avoid infinite loops in case we cannot find another
     * contiguous group to mutate
     */
    private static final int MAX_ATTEMPTS_PER_MUTATION = 10;

    private GeneListDao geneListDao;
    private Integer maxMutationsPerChromosome;

    @Override
    public void mutateChromosome(Chromosome chromosome) {
        if (maxMutationsPerChromosome == null) {
            throw new IllegalStateException("The maxMutationsPerChromosome cannot be null.");
        }

        /*
         * Choose a random number of mutations constrained by the configurable
         * max and the total number of genes
         */
        int numMutations = (int) (Math.random() * Math.min(maxMutationsPerChromosome, chromosome.getGenes().size()))
                + 1;

        Map<Integer, Integer> geneIndices = new HashMap<Integer, Integer>();
        for (int i = 0; i < numMutations; i++) {
            // Keep track of the mutated indices by passing the map by reference
            mutateRandomGeneGroup(chromosome, geneIndices);
        }
    }

    /**
     * Performs a genetic mutation of a random Gene group of the supplied
     * Chromosome
     * 
     * @param chromosome
     *            the Chromosome to mutate
     */
    private void mutateRandomGeneGroup(Chromosome chromosome, Map<Integer, Integer> geneIndices) {
        /*
         * Choose a random number of genes constrained by the static max and the
         * total number of genes
         */
        int numGenesToMutate = (int) (Math.random() * Math.min(MAX_GENES_PER_GROUP, chromosome.getGenes().size()))
                + 1;

        int randomIndex;

        // We don't want to reuse an index, so loop until we find a new one
        int attempts = 0;
        do {
            randomIndex = (int) (Math.random() * chromosome.getGenes().size());

            attempts++;

            if (attempts >= MAX_ATTEMPTS_PER_MUTATION) {
                if (log.isTraceEnabled()) {
                    log.trace("Exceeded " + MAX_ATTEMPTS_PER_MUTATION
                            + " attempts at finding a gene group to mutate.  Returning without performing mutation.");
                }

                return;
            }
        } while (exceedsChromosomeSize(chromosome, randomIndex + numGenesToMutate)
                || overlapsPreviousMutation(geneIndices, randomIndex, randomIndex + numGenesToMutate));

        mutateGeneGroup(chromosome, randomIndex, numGenesToMutate);

        geneIndices.put(randomIndex, randomIndex + numGenesToMutate - 1);
    }

    /**
     * Checks whether the length of the Chromosome is being exceeded by a
     * proposed end indice.
     * 
     * @param chromosome
     *            the Chromosome
     * @param proposedEndIndex
     *            the proposed end index
     * @return whether the proposed index exceeds the chromosome size
     */
    private static boolean exceedsChromosomeSize(Chromosome chromosome, int proposedEndIndex) {
        /*
         * proposedEndIndex is zero-indexed, so we need to subtract 1 from the
         * gene list size.
         */
        if (proposedEndIndex > chromosome.getGenes().size() - 1) {
            return true;
        }

        return false;
    }

    /**
     * Checks whether the proposed indices overlap a group which has already
     * been mutated so as not to re-mutate them.
     * 
     * @param geneIndices
     *            the Map of indices for groups already mutated previously
     * @param proposedBeginIndex
     *            the beginning index of a previously mutated group
     * @param proposedEndIndex
     *            the number of genes to mutate
     * @return whether the proposed indices overlap a group already mutated
     *         previously
     */
    private static boolean overlapsPreviousMutation(Map<Integer, Integer> geneIndices, Integer proposedBeginIndex,
            Integer proposedEndIndex) {
        Integer nextEndIndex = null;

        for (Integer nextBeginIndex : geneIndices.keySet()) {
            nextEndIndex = geneIndices.get(nextBeginIndex);

            // If the proposed begin index is anywhere within the previous group
            if (proposedBeginIndex >= nextBeginIndex && proposedBeginIndex <= nextEndIndex) {
                return true;
            }

            // If the proposed end index is anywhere within the previous group
            if (proposedEndIndex >= nextBeginIndex && proposedEndIndex <= nextEndIndex) {
                return true;
            }

            // If the proposed indices completely encompass the previous group
            if (proposedBeginIndex <= nextBeginIndex && proposedEndIndex >= nextEndIndex) {
                return true;
            }

            /*
             * Covers the case where one of the proposed indices is valid, but
             * the other exactly matches one from the previous group
             */
            if (proposedBeginIndex == nextBeginIndex || proposedBeginIndex == nextEndIndex
                    || proposedEndIndex == nextBeginIndex || proposedEndIndex == nextEndIndex) {
                return true;
            }
        }

        return false;
    }

    /**
     * Performs a genetic mutation of a specific Gene group of the supplied
     * Chromosome
     * 
     * @param chromosome
     *            the Chromosome to mutate
     * @param beginIndex
     *            the starting index of the Gene group to mutate
     */
    private void mutateGeneGroup(Chromosome chromosome, int beginIndex, int numGenes) {
        if ((beginIndex + numGenes) > chromosome.getGenes().size() - 1) {
            log.info("Attempted to mutate a Gene group in Chromosome with an end index of "
                    + (beginIndex + numGenes - 1) + " (zero-indexed), but the size is only "
                    + chromosome.getGenes().size() + ".  Cannot continue.");

            return;
        }

        int beginningSequenceIndex = chromosome.getGenes().get(beginIndex).getSequences().get(0).getSequenceId();

        // Remove the old genes
        List<Gene> genesRemoved = removeGenes(chromosome, beginIndex, numGenes);

        int sequencesRemoved = 0;

        // Count the number of sequences from all the removed genes
        for (Gene removedGene : genesRemoved) {
            sequencesRemoved += removedGene.size();
        }

        // Insert new random genes
        boolean successfullyMutated = insertRandomGenes(chromosome, beginIndex, beginningSequenceIndex,
                sequencesRemoved);

        if (!successfullyMutated) {
            /*
             * In certain edge cases, the geneListDao may be unable to return a
             * Gene
             */
            revertGenes(chromosome, genesRemoved, beginIndex);
        }
    }

    private static List<Gene> removeGenes(Chromosome chromosome, int beginIndex, int numGenes) {
        List<Gene> genesRemoved = new ArrayList<Gene>();

        for (int i = 0; i < numGenes; i++) {
            Gene removedGene = chromosome.getGenes().get(beginIndex).clone();

            chromosome.removeGene(beginIndex);

            genesRemoved.add(removedGene);
        }

        return genesRemoved;
    }

    private boolean insertRandomGenes(Chromosome chromosome, int beginGeneIndex, int beginningSequenceIndex,
            int sequencesRemoved) {
        int sequencesAdded = 0;

        List<Gene> genesToAdd = new ArrayList<Gene>();

        do {
            Gene geneToAdd = geneListDao.findRandomGene(chromosome, beginningSequenceIndex + sequencesAdded);

            if (geneToAdd.size() > (sequencesRemoved - sequencesAdded)) {
                geneToAdd = geneListDao.findRandomGeneOfLength(chromosome, beginningSequenceIndex + sequencesAdded,
                        sequencesRemoved - sequencesAdded);
            }

            if (geneToAdd == null) {
                return false;
            }

            genesToAdd.add(geneToAdd);

            sequencesAdded += geneToAdd.size();
        } while (sequencesAdded < sequencesRemoved);

        for (Gene geneToAdd : genesToAdd) {
            /*
             * We will keep adding them to the beginning of the group, which is
             * fine because they are random genes anyway
             */
            chromosome.insertGene(beginGeneIndex, geneToAdd);
        }

        return true;
    }

    /**
     * Reverts a group mutation
     * 
     * @param chromosome
     *            the chromosome to revert
     * @param genesRemoved
     *            the genes to re-add
     * @param beginIndex
     *            the index to start adding from
     */
    private static void revertGenes(Chromosome chromosome, List<Gene> genesRemoved, int beginIndex) {
        /*
         * Insert all of the removed genes back in their proper position.
         */
        for (int i = 0; i < genesRemoved.size(); i++) {
            chromosome.insertGene(beginIndex + i, genesRemoved.get(i));
        }
    }

    /**
     * @param geneListDao
     *            the geneListDao to set
     */
    @Required
    public void setGeneListDao(GeneListDao geneListDao) {
        this.geneListDao = geneListDao;
    }

    @Override
    public void setMaxMutationsPerChromosome(Integer maxMutationsPerChromosome) {
        this.maxMutationsPerChromosome = maxMutationsPerChromosome;
    }
}