adams.opt.optimise.genetic.AbstractGeneticAlgorithm.java Source code

Java tutorial

Introduction

Here is the source code for adams.opt.optimise.genetic.AbstractGeneticAlgorithm.java

Source

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

/*
 * AbstractGeneticAlgorithm.java
 * Copyright (C) 2009-2013 University of Waikato, Hamilton, New Zealand
 */

package adams.opt.optimise.genetic;

import java.util.BitSet;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Level;

import weka.core.Instances;
import adams.core.ClassLister;
import adams.core.Properties;
import adams.core.Range;
import adams.core.logging.LoggingHelper;
import adams.core.option.AbstractOptionConsumer;
import adams.core.option.ArrayConsumer;
import adams.core.option.OptionUtils;
import adams.env.Environment;
import adams.opt.optimise.AbstractOptimiser;

/**
 * Base class for genetic algorithms.
 *
 * @author Dale (dale at cs dot waikato dot ac dot nz)
 * @version $Revision$
 */
public abstract class AbstractGeneticAlgorithm extends AbstractOptimiser {

    /** for serialization. */
    private static final long serialVersionUID = 2823734145266194843L;

    /** the key for the relation name in the generated properties file.
     * @see #storeSetup(Instances,GeneticAlgorithmJob). */
    public final static String PROPS_RELATION = "relation";

    /** the key for a filter setup in the setup properties. */
    public final static String PROPS_FILTER = "filter";

    /** the key for the mask in the setup properties. */
    public final static String PROPS_MASK = "mask";

    /** number of genes per chromosome.
     * NB: must be initialized by the algorithm! */
    protected int m_NumGenes;

    /** number of chromosomes. */
    protected int m_NumChrom;

    /** the genes. */
    protected BitSet[] m_Genes;

    /** the fitness of the genes. */
    protected double[] m_Fitness;

    /** stores the best range of attribtues. */
    protected Range m_BestRange;

    /** whether to favor 0s instead of 1s. */
    protected boolean m_FavorZeroes;

    /** the seed value. */
    protected int m_Seed;

    /** the random number generator. */
    protected Random m_Random;

    /** whether the algorithm is still running. */
    protected boolean m_Running;

    /** the maximum number of seconds to train. */
    protected int m_MaxTrainTime;

    /** the time when training commenced. */
    protected long m_TrainStart;

    public abstract Vector<int[]> getInitialSetups();

    /** number of iterations. */
    protected int m_Iterations;

    /**
     * Initializes the members.
     */
    @Override
    protected void initialize() {
        super.initialize();

        m_NumGenes = 0; // must be set by the algorithm itself, e.g., in preRun()
        m_BestRange = new Range();
    }

    /**
     * Resets the genetic algorihtm.
     * Derived classes must call this method in set-methods of parameters to
     * assure the invalidation of previously generated data.
     */
    @Override
    public void reset() {
        super.reset();

        m_Random = new Random(m_Seed);
        m_Running = false;
    }

    /**
     * Adds options to the internal list of options.
     */
    @Override
    public void defineOptions() {
        super.defineOptions();

        m_OptionManager.add("num-chrom", "numChrom", 10);

        m_OptionManager.add("seed", "seed", 1);

        m_OptionManager.add("favor-zeroes", "favorZeroes", false);

        m_OptionManager.add("best", "bestRange", "-none-");

        m_OptionManager.add("max-train", "maxTrainTime", 0);

        m_OptionManager.add("iterations", "iterations", 1000);

    }

    /**
     * Sets the best range of attributes.
     *
     * @param value   the range
     */
    protected void setBestRange(Range value) {
        m_BestRange = value;
        reset();
    }

    /**
     * Sets the best range of attributes.
     *
     * @param value   the range
     */
    public void setBestRange(String value) {
        if (value.equals("-none-"))
            setBestRange(new Range());
        else
            setBestRange(new Range(value));
    }

    /**
     * Returns the best range of attributes.
     *
     * @return      the range
     */
    public String getBestRange() {
        if (m_BestRange.getRange().length() == 0)
            return "-none-";
        else
            return m_BestRange.getRange();
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String bestRangeTipText() {
        return "The range of the best attributes.";
    }

    /**
     * Sets the seed value to use, resets the random number generator.
     *
     * @param value   the seed to use
     */
    public void setSeed(int value) {
        m_Seed = value;
        reset();
    }

    /**
     * Returns the current seed value.
     *
     * @return      the seed value
     */
    public int getSeed() {
        return m_Seed;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String seedTipText() {
        return "The seed value for the random number generator.";
    }

    /**
     * Sets whether 0s are favored over 1s.
     *
     * @param value   if true then 0s are favored over 1s
     */
    public void setFavorZeroes(boolean value) {
        m_FavorZeroes = value;
        reset();
    }

    /**
     * Returns whether 0s are favored over 1s.
     *
     * @return      true if 0s are favored over 1s
     */
    public boolean getFavorZeroes() {
        return m_FavorZeroes;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String favorZeroesTipText() {
        return "Whether to favor 0s instead of 1s.";
    }

    /**
     * Returns the number of genes to use.
     *
     * @return      the number
     */
    public int getNumGenes() {
        return m_NumGenes;
    }

    /**
     * Sets the number of chromosomes to use.
     *
     * @param value   the number
     */
    public void setNumChrom(int value) {
        m_NumChrom = value;
        reset();
    }

    /**
     * Returns the number of chromosomes to use.
     *
     * @return      the number
     */
    public int getNumChrom() {
        return m_NumChrom;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String numChromTipText() {
        return "The number of chromosomes, ie, the population size.";
    }

    /**
     * Sets the maximum number of seconds to perform training.
     *
     * @param value   the number of seconds
     */
    public void setMaxTrainTime(int value) {
        m_MaxTrainTime = value;
        reset();
    }

    /**
     * Returns the maximum number of seconds to perform training.
     *
     * @return      the seed value
     */
    public int getMaxTrainTime() {
        return m_MaxTrainTime;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String maxTrainTimeTipText() {
        return "The maximum number of seconds to training time (0 = unlimited time).";
    }

    /**
     * Stops the execution of the algorithm.
     */
    public void stop() {
        m_Running = false;
    }

    /**
     * Returns whether the algorithm is still running.
     *
     * @return      true if the algorithm is still running
     */
    public boolean isRunning() {
        return m_Running;
    }

    /**
     * Returns the current fitness values.
     *
     * @return      the fitness values
     */
    public double[] getFitness() {
        return m_Fitness;
    }

    /**
     * Sets the iterations to use.
     *
     * @param value    the iterations.
     */
    public void setIterations(int value) {
        m_Iterations = value;
    }

    /**
     * Gets the iterations use.
     *
     * @return      the iterations
     */
    public int getIterations() {
        return m_Iterations;
    }

    /**
     * Returns the tip text for this property.
     *
     * @return       tip text for this property suitable for
     *          displaying in the GUI or for listing the options.
     */
    public String iterationsTipText() {
        return "The number of iterations to use.";
    }

    /**
     * Initializes the algorithm.
     *
     * @param ch      the chromosome
     * @param genes   the number of genes
     */
    protected void init(int ch, int genes) {
        m_NumChrom = ch;
        m_NumGenes = genes;
        getLogger().info("Numchrom=" + ch + "Numgene=" + genes);
        m_Genes = new BitSet[m_NumChrom];
        Vector<int[]> setups = getInitialSetups();
        for (int i = 0; i < m_NumChrom; i++) {
            m_Genes[i] = new BitSet(m_NumGenes);

            if (i < setups.size()) {
                int[] gene = setups.get(i);
                for (int j = 0; j < m_NumGenes; j++) {
                    if (gene[j] == 1) {
                        m_Genes[i].set(j);
                    }
                }
                continue;
            }
            for (int j = 0; j < m_NumGenes; j++) {
                double set = m_Random.nextDouble();
                if (set < 0.5) {
                    m_Genes[i].set(j);
                }
            }

        }

        m_Fitness = new double[m_NumChrom];
        for (int f = 0; f < m_NumChrom; f++)
            m_Fitness[f] = 0;
        sort();
    }

    /**
     * Returns the value of the specified gene.
     *
     * @param chromosome   the chromosome index
     * @param gene   the gene index
     * @return      true if gene is active
     */
    public boolean getGene(int chromosome, int gene) {
        return m_Genes[chromosome].get(gene);
    }

    /**
     * Sets the value of the specified gene.
     *
     * @param chromosome   the chromosome index
     * @param gene   the gene index
     * @param value   the value to set, 0=clear, otherwise activate it
     */
    public void setGene(int chromosome, int gene, int value) {
        setGene(chromosome, gene, (value != 0));
    }

    /**
     * Sets the value of the specified gene.
     *
     * @param chromosome   the chromosome index
     * @param gene   the gene index
     * @param value   false=clear, true=activate it
     */
    public void setGene(int chromosome, int gene, boolean value) {
        if (value)
            m_Genes[chromosome].set(gene);
        else
            m_Genes[chromosome].clear(gene);
    }

    /**
     * return if a has more zeroes than b.
     *
     * @param a      the first gene
     * @param b      the second gene
     * @return      true if a has more zeroes than b
     */
    public boolean hasMoreZeroes(BitSet a, BitSet b) {
        return (a.cardinality() < b.cardinality());
    }

    /**
     * Sorts genes and fitness arrays according to fitness.
     */
    public void sort() {
        BitSet btemp;
        for (int c = 0; c < m_NumChrom; c++) {
            for (int d = (m_NumChrom - 2); d >= c; d--) {
                if (m_Fitness[d] < m_Fitness[d + 1]) {
                    btemp = m_Genes[d];
                    double x = m_Fitness[d];
                    m_Genes[d] = m_Genes[d + 1];
                    m_Fitness[d] = m_Fitness[d + 1];
                    m_Genes[d + 1] = btemp;
                    m_Fitness[d + 1] = x;
                } else if (m_Fitness[d] == m_Fitness[d + 1] && hasMoreZeroes(m_Genes[d + 1], m_Genes[d])) {
                    btemp = m_Genes[d];
                    double x = m_Fitness[d];
                    m_Genes[d] = m_Genes[d + 1];
                    m_Fitness[d] = m_Fitness[d + 1];
                    m_Genes[d + 1] = btemp;
                    m_Fitness[d + 1] = x;
                }
            }
        }
    }

    /**
     * Performs cross-over.
     */
    public void doCrossovers() {
        for (int m = 0; m < m_NumChrom / 4; m++) {
            copyGene(m + m_NumChrom * 3 / 4, m);
            // copies first chromosome half over last half
        }

        // copy the 2 best genes so that their
        // genetic material is replicated frequently:
        if (m_NumChrom > 4) {
            for (int i = 0; i < m_NumGenes; i++) {
                setGene(m_NumChrom - 1, i, getGene(0, i));
                // Seems redundant but slightly different...
                setGene(m_NumChrom - 2, i, getGene(0, i));
                // when compared with the CopyGene command.
                setGene(m_NumChrom - 3, i, getGene(0, i));
                // Do one or the other.
                setGene(m_NumChrom - 4, i, getGene(1, i));
                setGene(m_NumChrom - 5, i, getGene(1, i));
            }
        }

        int num = m_NumChrom / 4; //originally set to /4
        for (int i = 0; i < num; i++) {
            int c1 = 2 + (int) ((m_NumChrom - 2) * m_Random.nextDouble() * 0.99);
            int c2 = 2 + (int) ((m_NumChrom - 2) * m_Random.nextDouble() * 0.99);
            // finds two different chromosomes
            if (c1 != c2) {
                int locus = 2 + (int) ((m_NumGenes - 3) * m_Random.nextDouble());
                // finds a place within
                for (int g = 0; g < locus; g++) {
                    boolean temp = getGene(c1, i);
                    // and recombines by switching genes
                    setGene(c1, i, getGene(c2, i));
                    setGene(c2, i, temp);
                }
            }
        }
    }

    /**
     * Copies the values of one gene to another.
     *
     * @param to      the gene to copy to
     * @param from   the gene to copy from
     */
    protected void copyGene(int to, int from) {
        for (int i = 0; i < m_NumGenes; i++) {
            if (getGene(from, i))
                setGene(to, i, 1);
            else
                setGene(to, i, 0);
        }
    }

    /**
     * Performs mutations.
     */
    public void doMutations() {
        for (int i = 0; i < m_NumChrom * 0.5; i++) {
            int c = 0 + (int) ((m_NumChrom - 2) * m_Random.nextDouble() * 0.95); //0-->2
            int g = (int) (m_NumGenes * m_Random.nextDouble() * 0.95); //saves good genes
            if (getGene(c, g))
                setGene(c, g, 0);
            // switch gene from its present position
            else
                setGene(c, g, 1);
        }
    }

    /**
     * Performs mutations.
     */
    public void doMutations2() {
        for (int i = 0; i < m_NumChrom * 0.5; i++) {
            int c = 2 + (int) ((m_NumChrom - 2) * m_Random.nextDouble() * 0.99); //0-->2

            double thresh = 1.0 / (double) m_NumGenes;
            for (int gn = 0; gn < m_NumGenes; gn++) {
                if (m_Random.nextDouble() < thresh) {
                    if (getGene(c, gn))
                        setGene(c, gn, 0);
                    // switch gene from its present position
                    else
                        setGene(c, gn, 1);
                }
            }
        }
    }

    /**
     * Override the following function in sub-classes.
     */
    public abstract void calcFitness();

    /**
     * Generates a Properties file that stores information on the setup of
     * the genetic algorithm. E.g., it backs up the original relation name.
     * The generated properties file will be used as new relation name for
     * the data. Derived classes can add additional parameters to this
     * properties file.
     *
     * @param data   the data to create the setup for
     * @param job      the associated job
     * @see      #PROPS_RELATION
     * @return      the generated setup
     */
    protected Properties storeSetup(Instances data) {
        Properties result;

        result = new Properties();

        // relation name
        result.setProperty(PROPS_RELATION, data.relationName());

        // filter (default is empty)
        result.setProperty(PROPS_FILTER, "");

        return result;
    }

    /**
     * Creates a new dataset, with the setup as the new relation name.
     *
     * @param data   the data to replace the relation name with the setup
     * @param job      the associated job
     * @return      the updated dataset
     */
    protected Instances updateHeader(Instances data) {
        Properties props;

        props = storeSetup(data);
        data.setRelationName(props.toString());

        return data;
    }

    /**
     * Further initializations in derived classes.
     */
    protected void preRun() {
        m_Running = true;
        m_TrainStart = System.currentTimeMillis();
        if (LoggingHelper.isAtLeast(getLogger(), Level.FINE))
            getLogger().fine("Size preRun: " + sizeOf());
    }

    /**
     * Runs the genetic algorithm.
     *
     * @return      true if successfully finished, false when interrupted
     */
    public boolean run() {
        int i;
        int cx;
        int po;
        boolean result;

        result = true;

        try {
            preRun();
        } catch (Exception e) {
            getLogger().log(Level.SEVERE, "Error in preRun", e);
            result = false;
        }

        if (result) {

            try {
                for (i = 0; i < getIterations(); i++) {
                    if (i % 1000 == 0) {
                        System.out.println("Iteration " + (i + 1) + "/" + getIterations());
                    }
                    calcFitness();
                    sort();

                    if (isLoggingEnabled()) {
                        getLogger().info("Generation " + String.valueOf(i));
                        for (cx = 0; cx < getNumChrom(); cx++) {
                            System.out.print(" Fitness for chromosome ");
                            if (cx != -1) {
                                for (po = 0; po < getNumGenes(); po++) {
                                    if (getGene(cx, po)) {
                                        System.out.print("1");
                                    } else {
                                        System.out.print("0");
                                    }
                                }
                            }
                            System.out.print("--->" + getFitness()[cx] + "\n");
                        }
                    }

                    doCrossovers();
                    doMutations2();

                    // time limit exceeded?
                    if (m_MaxTrainTime > 0) {
                        if ((double) (System.currentTimeMillis() - m_TrainStart) / 1000.0 >= m_MaxTrainTime) {
                            getLogger().info(
                                    "Training time limit of " + m_MaxTrainTime + " seconds exceeded - stopping.");
                            break;
                        }
                    }

                    if (!isRunning()) {
                        getLogger().severe("Interrupted!");
                        break;
                    }
                }
            } catch (Exception e) {
                result = false;
                getLogger().log(Level.SEVERE, "Error in iteration", e);
            }
        }

        try {
            postRun();
        } catch (Exception e) {
            result = false;
            getLogger().log(Level.SEVERE, "Error in postRun", e);
        }

        return result;
    }

    /**
     * Further clean-ups in derived classes.
     *
     * @throws Exception   if something goes wrong
     */
    protected void postRun() throws Exception {
        m_Running = false;
        if (LoggingHelper.isAtLeast(getLogger(), Level.FINE))
            getLogger().fine("Size postRun: " + sizeOf());
    }

    /**
     * Runs the genetic algorithm with the given options.
     *
     * @param env      the environment class to use
     * @param genetic   the genetic algorithm to run
     * @param options   the options to set
     */
    public static void runGeneticAlgorithm(Class env, Class genetic, String[] options) {
        AbstractGeneticAlgorithm geneticInst;

        Environment.setEnvironmentClass(env);

        try {
            if (OptionUtils.helpRequested(options)) {
                System.out.println("Help requested...\n");
                geneticInst = forName(genetic.getName(), new String[0]);
                System.out.println("\n" + OptionUtils.list(geneticInst));
            } else {
                geneticInst = forName(genetic.getName(), options);
                geneticInst.run();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Returns a list with classnames of genetic algorithms.
     *
     * @return      the genetic algorithm classnames
     */
    public static String[] getGeneticAlgorithms() {
        return ClassLister.getSingleton().getClassnames(AbstractGeneticAlgorithm.class);
    }

    /**
     * Instantiates the genetic algorithm with the given options.
     *
     * @param classname   the classname of the genetic algorithm to instantiate
     * @param options   the options for the genetic algorithm
     * @return      the instantiated genetic algorithm or null if an error occurred
     */
    public static AbstractGeneticAlgorithm forName(String classname, String[] options) {
        AbstractGeneticAlgorithm result;

        try {
            result = (AbstractGeneticAlgorithm) OptionUtils.forName(AbstractGeneticAlgorithm.class, classname,
                    options);
        } catch (Exception e) {
            e.printStackTrace();
            result = null;
        }

        return result;
    }

    /**
     * Instantiates the genetic algorithm from the given commandline
     * (i.e., classname and optional options).
     *
     * @param cmdline   the classname (and optional options) of the
     *          genetic algorithm to instantiate
     * @return      the instantiated genetic algorithm
     *          or null if an error occurred
     */
    public static AbstractGeneticAlgorithm forCommandLine(String cmdline) {
        return (AbstractGeneticAlgorithm) AbstractOptionConsumer.fromString(ArrayConsumer.class, cmdline);
    }
}