/*
* Copyright 2007-2013
* Licensed under GNU Lesser General Public License
*
* This file is part of EpochX
*
* 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.fitness;
import static org.epochx.Config.Template.TEMPLATE;
import org.epochx.Config;
import org.epochx.Config.ConfigKey;
import org.epochx.Individual;
import org.epochx.cfg.CFGIndividual;
import org.epochx.event.ConfigEvent;
import org.epochx.event.EventManager;
import org.epochx.event.Listener;
import org.epochx.fitness.DoubleFitness;
import org.epochx.interpret.Interpreter;
import org.epochx.interpret.MalformedProgramException;
/**
* A fitness function for <code>GEIndividual</code>s that calculates and assigns
* <code>DoubleFitness.Minimise</code> scores. The fitness scores are calculated by executing
* the program for each of the provided sets of inputs. The difference between the value
* returned by the program and the expected outputs supplied is summed to give the fitness
* value.
*
* When using this fitness function the {@link #INPUT_VARIABLES}, {@link #INPUT_VALUE_SETS}
* and {@link #EXPECTED_OUTPUTS} config options must be set, or the same values set using the
* mutator methods provided. The length of the <code>INPUT_VALUE_SETS</code> array should match
* the length of the <code>EXPECTED_OUTPUTS</code> array and the number of values in each set
* should match the length of the <code>INPUT_IDENTIFIERS</code> array.
*
* If the program returns <code>NaN</code> for any of the input sets then a fitness score of
* <code>NaN</code> is assigned by default, although this can be changed by overriding the
* <code>nanFitnessScore</code> method.
*
* @since 2.0
*/
public class SumOfError extends CFGFitnessFunction implements Listener<ConfigEvent> {
/**
* The key for setting the expected output values from the programs being evaluated
*/
public static final ConfigKey<Double[]> EXPECTED_OUTPUTS = new ConfigKey<Double[]>();
// Configuration settings
private Interpreter<CFGIndividual> interpreter;
private String[] argNames;
private Object[][] inputValueSets;
private Double[] expectedOutputs;
private Double malformedPenalty;
/**
* Constructs a <code>SumOfError</code> fitness function with control parameters
* automatically loaded from the config.
*/
public SumOfError() {
this(true);
}
/**
* Constructs a <code>SumOfError</code> fitness function 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 SumOfError(boolean autoConfig) {
// Default config values
malformedPenalty = Double.MAX_VALUE;
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 #INPUT_IDENTIFIERS}
* <li>{@link #INPUT_VALUE_SETS}
* <li>{@link #EXPECTED_OUTPUTS}
* <li>{@link #INTERPRETER}
* <li>{@link #MALFORMED_PENALTY}
* </ul>
*/
protected void setup() {
argNames = Config.getInstance().get(INPUT_IDENTIFIERS);
inputValueSets = Config.getInstance().get(INPUT_VALUE_SETS);
expectedOutputs = Config.getInstance().get(EXPECTED_OUTPUTS);
interpreter = Config.getInstance().get(INTERPRETER);
malformedPenalty = Config.getInstance().get(MALFORMED_PENALTY, malformedPenalty);
}
/**
* Receives configuration events and triggers this fitness function to
* configure its parameters 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, INPUT_IDENTIFIERS, INPUT_VALUE_SETS, EXPECTED_OUTPUTS, INTERPRETER, MALFORMED_PENALTY)) {
setup();
}
}
/**
* Calculates the fitness of the given individual. This fitness function only operates
* on <code>GEIndividual</code>s with a <code>Double</code> return type. The fitness
* returned will be an instance of <code>DoubleFitness.Minimise</code>. The fitness
* score is calculated as the sum of the difference between the expected outputs and
* the actual
* outputs, for each set of inputs.
*
* @param individual the individual to evaluate the fitness of
* @return the fitness of the given individual
* @throws IllegalArgumentException if the individual is not a GEIndividual or the
* individual's data-type is not Double.
*/
@Override
public DoubleFitness.Minimise evaluate(Individual individual) {
if (!(individual instanceof CFGIndividual)) {
throw new IllegalArgumentException("Unsupported representation");
}
CFGIndividual program = (CFGIndividual) individual;
Double errorSum = 0.0;
Object[] results;
try {
results = interpreter.eval(program, argNames, inputValueSets);
} catch (MalformedProgramException e) {
return new DoubleFitness.Minimise(malformedPenalty);
}
for (int i = 0; i < expectedOutputs.length; i++) {
Object result = results[i];
if (result instanceof Double) {
double d = (Double) result;
if (!Double.isNaN(d)) {
double error = Math.abs(d - expectedOutputs[i]);
errorSum += error;
} else {
errorSum = nanFitnessScore();
break;
}
}
}
return new DoubleFitness.Minimise(errorSum);
}
/**
* Returns the value to be used when an individual returns a NaN value. The default value
* is <code>Double.NaN</code>.
*
* @return the value to return when an individual returns a NaN value
*/
protected Double nanFitnessScore() {
return Double.NaN;
}
/**
* Gets the names of the input variables
*
* @return an array of the input variable names
*/
public String[] getInputIdentifiers() {
return argNames;
}
/**
* Sets the names of the input variables.
*
* If automatic configuration is enabled then any value set here will be overwritten
* by the {@link #INPUT_IDENTIFIERS} configuration setting on the next config event.
*
* @param argNames the names of the input variables
*/
public void setInputIdentifiers(String[] argNames) {
this.argNames = argNames;
}
/**
* Returns the sets of input values.
*
* @return the sets of input values
*/
public Object[][] getInputValueSets() {
return inputValueSets;
}
/**
* Sets the sets of input values. The length of the array should match the length
* of the expected outputs array. Each set of values should have the same number of
* values, equal to the length of the input identifiers array.
*
* If automatic configuration is enabled then any value set here will be overwritten
* by the {@link #INPUT_VALUE_SETS} configuration setting on the next config event.
*
* @param inputValueSets the sets of input values
*/
public void setInputValueSets(Object[][] inputValueSets) {
this.inputValueSets = inputValueSets;
}
/**
* Returns the expected outputs that the actual outputs will be compared against
*
* @return the expected outputs for the input sets
*/
public Double[] getExpectedOutputs() {
return expectedOutputs;
}
/**
* Sets the expected outputs to compare against. The length of the array should
* match the length of the input values array.
*
* If automatic configuration is enabled then any value set here will be
* overwritten by the {@link #EXPECTED_OUTPUTS} configuration setting on the
* next config event.
*
* @param expectedOutputs the expected outputs to compare against
*/
public void setExpectedOutputs(Double[] expectedOutputs) {
this.expectedOutputs = expectedOutputs;
}
/**
* Returns the fitness score that will be assigned to individuals that are
* malformed
*
* @return the penalty to be assigned to malformed individuals
*/
public Double getMalformedProgramPenalty() {
return malformedPenalty;
}
/**
* Sets the fitness score to be assigned to individuals that are malformed.
*
* If automatic configuration is enabled then any value set here will be
* overwritten by the {@link #MALFORMED_PENALTY} configuration setting on the
* next config event.
*
* @param malformedPenalty
*/
public void setMalformedProgramPenalty(Double malformedPenalty) {
this.malformedPenalty = malformedPenalty;
}
/**
* Returns the interpreter being used to execute individuals
*
* @return the interpreter in use
*/
public Interpreter<CFGIndividual> getInterpreter() {
return interpreter;
}
/**
* Sets the interpreter to use to execute individuals
*
* @param interpreter the interpreter to execute individuals with
*/
public void setInterpreter(Interpreter<CFGIndividual> interpreter) {
this.interpreter = interpreter;
}
}