/*
* Copyright 2007-2013
* Licensed under GNU Lesser General Public License
*
* This file is part of EpochX: genetic programming software for research
*
* EpochX is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EpochX 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with EpochX. If not, see <http://www.gnu.org/licenses/>.
*
* The latest version is available from: http://www.epochx.org
*/
package org.epochx.cfg.init;
import static org.epochx.Config.Template.TEMPLATE;
import static org.epochx.Population.SIZE;
import static org.epochx.RandomSequence.RANDOM_SEQUENCE;
import static org.epochx.cfg.CFGIndividual.MAXIMUM_DEPTH;
import static org.epochx.cfg.init.RampedHalfAndHalf.Method.FULL;
import static org.epochx.cfg.init.RampedHalfAndHalf.Method.GROW;
import static org.epochx.grammar.Grammar.GRAMMAR;
import org.epochx.Config;
import org.epochx.Config.ConfigKey;
import org.epochx.InitialisationMethod;
import org.epochx.Population;
import org.epochx.RandomSequence;
import org.epochx.cfg.CFGIndividual;
import org.epochx.event.ConfigEvent;
import org.epochx.event.EventManager;
import org.epochx.event.InitialisationEvent;
import org.epochx.event.Listener;
import org.epochx.grammar.Grammar;
/**
* Initialisation implementation which uses a combination of full and grow
* initialisers to create an initial population of <code>CFGIndividual</code>s.
*
* <p>
* Depths are equally split between depths from the minimum initial depth
* attribute up to the maximum initial depth. Initialisation of individuals at
* each of these depths is then alternated between full and grow initialisers
* starting with grow.
*
* <p>
* There will not always be an equal number of programs created to each depth,
* this will depend on if the population size is exactly divisible by the range
* of depths (<code>initial maximum depth - initial minimum depth</code>). If
* the range of depths is greater than the population size then some depths will
* not occur at all in order to ensure as wide a spread of depths up to the
* maximum as possible.
*
* <p>
* See the {@link #setup()} method documentation for a list of configuration
* parameters used to control this operator.
*
* @see Full
* @see Grow
*
* @since 2.0
*/
public class RampedHalfAndHalf implements CFGInitialisation, Listener<ConfigEvent> {
/**
* The key for setting and retrieving the smallest maximum depth setting
* from which the ramping will begin
*/
public static final ConfigKey<Integer> RAMPING_START_DEPTH = new ConfigKey<Integer>();
// The two initialisation methods to ramp
private final Grow grow;
private final Full full;
// Configuration settings
private RandomSequence random;
private Grammar grammar;
private Integer populationSize;
private Boolean allowDuplicates;
private Integer endDepth;
private Integer startDepth;
/**
* Initialisation method labels
*/
public enum Method {
GROW, FULL;
}
/**
* Constructs a <code>RampedHalfAndHalfInitialisation</code> with control parameters
* automatically loaded from the config
*/
public RampedHalfAndHalf() {
this(true);
}
/**
* Constructs a <code>RampedHalfAndHalfInitialisation</code> with control parameters
* initially loaded from the config. If the <code>autoConfig</code> argument is
* set to <code>true</code> then the configuration will be automatically updated
* when the config is modified.
*
* @param autoConfig whether this operator should automatically update its
* configuration settings from the config
*/
public RampedHalfAndHalf(boolean autoConfig) {
grow = new Grow(false);
full = new Full(false);
// Default config values
allowDuplicates = true;
setup();
if (autoConfig) {
EventManager.getInstance().add(ConfigEvent.class, this);
}
}
/**
* Sets up this operator with the appropriate configuration settings.
* This method is called whenever a <code>ConfigEvent</code> occurs for a
* change in any of the following configuration parameters:
* <ul>
* <li>{@link RandomSequence#RANDOM_SEQUENCE}
* <li>{@link Population#SIZE}
* <li>{@link InitialisationMethod#ALLOW_DUPLICATES} (default: <code>true</code>)
* <li>{@link Grammar#GRAMMAR}
* <li>{@link #RAMPING_START_DEPTH}
* <li>{@link CFGIndividual#MAXIMUM_DEPTH}
* </ul>
*/
protected void setup() {
random = Config.getInstance().get(RANDOM_SEQUENCE);
populationSize = Config.getInstance().get(SIZE);
allowDuplicates = Config.getInstance().get(ALLOW_DUPLICATES, allowDuplicates);
grammar = Config.getInstance().get(GRAMMAR);
startDepth = Config.getInstance().get(RAMPING_START_DEPTH);
endDepth = Config.getInstance().get(MAXIMUM_DEPTH);
}
/**
* Receives configuration events and triggers this operator to reconfigure
* if the <code>ConfigEvent</code> is for one of its required parameters
*
* @param event {@inheritDoc}
*/
@Override
public void onEvent(ConfigEvent event) {
if (event.isKindOf(TEMPLATE, RANDOM_SEQUENCE, SIZE, ALLOW_DUPLICATES, GRAMMAR, RAMPING_START_DEPTH, MAXIMUM_DEPTH)) {
setup();
}
}
/**
* Creates a new population of <code>CFGIndividual</code>s. Will use grow initialisation to construct half
* the population and full to create the other half. If the population size is an odd number then the extra
* individual will be initialised with grow.
*
* @return a new population of individuals
*/
@Override
public Population createPopulation() {
EventManager.getInstance().fire(new InitialisationEvent.StartInitialisation());
if (endDepth < startDepth) {
throw new IllegalStateException("end depth must be greater than the start depth");
}
// Create population list to populate
Population population = new Population();
int currentDepth = startDepth;
int minDepthPossible = grammar.getMinimumDepth();
if (currentDepth < minDepthPossible) {
// Our start depth can only be as small as the grammars minimum depth
currentDepth = minDepthPossible;
}
if (endDepth < startDepth) {
throw new IllegalStateException("end maximum depth must be greater than the minimum possible depth");
}
// Number of individuals each depth SHOULD have. But won't exactly unless the remainder is 0.
double individualsPerDepth = (double) populationSize / (endDepth - startDepth + 1);
// Whether each program was grown or not (full)
Method[] method = new Method[populationSize];
for (int i = 0; i < populationSize; i++) {
// Calculate depth
int depth = (int) Math.floor((population.size() / individualsPerDepth) + currentDepth);
// Grow on even numbers, full on odd
CFGIndividual individual;
do {
if ((i % 2) == 0) {
method[i] = GROW;
grow.setMaximumDepth(depth);
individual = grow.createIndividual();
} else {
method[i] = FULL;
full.setDepth(depth);
individual = full.createIndividual();
}
} while (!allowDuplicates && population.contains(individual));
population.add(individual);
}
EventManager.getInstance().fire(new EndEvent(population, method));
return population;
}
/**
* Constructs a new <code>CFGIndividual</code> instance using either a full or grow
* initialisation procedure, selected at random
*
* @return a new individual
*/
@Override
public CFGIndividual createIndividual() {
if (random.nextBoolean()) {
return grow.createIndividual();
} else {
return full.createIndividual();
}
}
/**
* Returns whether or not duplicates are currently allowed in generated
* populations
*
* @return <code>true</code> if duplicates are currently allowed in populations
* generated by the <code>createPopulation</code> method and
* <code>false</code> otherwise
*/
public boolean isDuplicatesEnabled() {
return allowDuplicates;
}
/**
* Sets whether duplicates should be allowed in populations that are
* generated. If automatic configuration is enabled then any value set here
* will be overwritten by the {@link InitialisationMethod#ALLOW_DUPLICATES}
* configuration setting on the next config event.
*
* @param allowDuplicates whether duplicates should be allowed in
* populations that are generated
*/
public void setDuplicatesEnabled(boolean allowDuplicates) {
this.allowDuplicates = allowDuplicates;
}
/**
* Returns the random number sequence in use
*
* @return the currently set random sequence
*/
public RandomSequence getRandomSequence() {
return random;
}
/**
* Sets the random number sequence to use. If automatic configuration is
* enabled then any value set here will be overwritten by the
* {@link RandomSequence#RANDOM_SEQUENCE} configuration setting on the next
* config event.
*
* @param random the random number generator to set
*/
public void setRandomSequence(RandomSequence random) {
this.random = random;
//grow.setRandomSequence(random);
full.setRandomSequence(random);
}
/**
* Returns the grammar that the <code>CFGIndividual</code>s will satisfy with
* their parse trees
*
* @return the currently set grammar
*/
public Grammar getGrammar() {
return grammar;
}
/**
* Sets the grammar to be satisfied by the full parse trees of the new
* <code>CFGIndividual</code>s. If automatic configuration is enabled then any
* value set here will be overwritten by the {@link Grammar#GRAMMAR}
* configuration setting on the next config event.
*
* @param grammar the grammar to set
*/
public void setGrammar(Grammar grammar) {
this.grammar = grammar;
grow.setGrammar(grammar);
full.setGrammar(grammar);
}
/**
* Returns the number of individuals to be generated in a population created
* by the <code>createPopulation</code> method
*
* @return the size of the populations generated
*/
public int getPopulationSize() {
return populationSize;
}
/**
* Sets the number of individuals to be generated in a population created
* by the <code>createPopulation</code> method. If automatic configuration is
* enabled thenAny value set here will be overwritten by the
* {@link Population#SIZE} configuration setting on the next config event.
*
* @param size the size of the populations generated
*/
public void setPopulationSize(int size) {
this.populationSize = size;
}
/**
* Returns the depth that the maximum depth is ramped up to
*
* @return the maximum setting the depth will be ramped to
*/
public int getEndDepth() {
return endDepth;
}
/**
* Sets the depth that the maximum depth will be ramped up to when a
* population is created with the <code>createPopulation</code> method. If
* automatic configuration is enabled, then any value set here will be
* overwritten by the {@link CFGIndividual#MAXIMUM_DEPTH} setting.
*
* @param endDepth the maximum setting to ramp the depth to
*/
public void setEndDepth(int endDepth) {
this.endDepth = endDepth;
}
/**
* Returns the depth that the maximum depth is ramped up from
*
* @return the initial maximum depth setting before being ramped
*/
public int getStartDepth() {
return startDepth;
}
/**
* Sets the depth that the maximum depth will be ramped up from when a
* population is created with the <code>createPopulation</code> method. If
* automatic configuration is enabled, then any
* value set here will be overwritten by the
* {@link RampedHalfAndHalf#RAMPING_START_DEPTH} configuration
* setting on the next config event.
*
* @param startDepth the initial maximum depth setting before being ramped
*/
public void setStartDepth(int startDepth) {
this.startDepth = startDepth;
}
/**
* An event fired at the end of a ramped half-and-half population initialisation
*
* @see RampedHalfAndHalf
*
* @since 2.0
*/
public class EndEvent extends InitialisationEvent.EndInitialisation {
private Method[] method;
/**
* Constructs an event with the population that was constructed by the
* ramped half-and-half initialisation procedure and a listing of the
* initialisation method used for each individual in that population
*
* @param population the population of individuals created by the
* initialisation procedure
* @param method an array listing the initialisation methods used
*/
public EndEvent(Population population, Method[] method) {
super(population);
this.method = method;
}
/**
* Returns an array listing the initialisation method used for each
* individual in the population
*
* @return an array listing the initialisation methods used
*/
public Method[] getMethod() {
return method;
}
}
}