From 36f4393bcc9e55afa2334baa33e603ce839741a1 Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Thu, 1 May 2014 13:05:27 +0100 Subject: Did more commenting, implemented reflection and statistics --- src/jcgp/backend/modules/Module.java | 67 ++++++++-- .../backend/modules/es/EvolutionaryStrategy.java | 12 +- src/jcgp/backend/modules/es/MuPlusLambda.java | 32 ++--- .../backend/modules/es/TournamentSelection.java | 33 ++--- .../backend/modules/mutator/FixedPointMutator.java | 35 ++--- src/jcgp/backend/modules/mutator/Mutator.java | 10 +- .../modules/mutator/PercentPointMutator.java | 40 ++---- src/jcgp/backend/modules/mutator/PointMutator.java | 6 +- .../modules/mutator/ProbabilisticMutator.java | 33 ++--- .../modules/problem/DigitalCircuitProblem.java | 43 ++++-- src/jcgp/backend/modules/problem/Problem.java | 142 +++++++++++++++++--- .../modules/problem/SymbolicRegressionProblem.java | 72 +++++++--- .../backend/modules/problem/TestCaseProblem.java | 148 +++++++++++++-------- .../modules/problem/TravellingSalesmanProblem.java | 35 +++-- 14 files changed, 453 insertions(+), 255 deletions(-) (limited to 'src/jcgp/backend/modules') diff --git a/src/jcgp/backend/modules/Module.java b/src/jcgp/backend/modules/Module.java index 7efbf3a..b53184e 100644 --- a/src/jcgp/backend/modules/Module.java +++ b/src/jcgp/backend/modules/Module.java @@ -1,23 +1,74 @@ package jcgp.backend.modules; -import jcgp.backend.resources.parameters.Parameter; +import java.util.ArrayList; + +import jcgp.backend.parameters.Parameter; /** - * This interface defines the expected behaviour of a module. Specifically, a module - * is expected to be able to return a list of local parameters. When a user interface - * is used, it is expected to display the parameters of each module and allow user - * interaction for parameters which are not monitors. + * This class defines the expected behaviour of a module. Generally, modules + * are entities which contain parameters; these can be retrieved using + * {@code getLocalParameters()}. GUIs should make use of this getter + * to display visual parameter controls to users. Subclasses don't have direct + * access to the list; instead they must use {@code registerParameter()} (ideally + * in the constructor) to make sure the parameters are returned. + *
+ * In addition, implementations of {@code Module} should specify a module name + * in their constructor using {@code setName()}. If a name is not provided, + * the simple name of the class will be used. * * @see Parameter - * * @author Eduardo Pedroni * */ -public interface Module { +public abstract class Module { + private ArrayList> localParameters = new ArrayList>(); + private String name = getClass().getSimpleName(); + /** + * This method is used by the GUI in order to build visual + * representations of all parameters used by the module. + * Therefore, any parameters returned here will be displayed + * visually. + * * @return a list of generic parameters exposed by the module. */ - public abstract Parameter[] getLocalParameters(); + public final ArrayList> getLocalParameters() { + return localParameters; + } + + /** + * Adds one or more parameters to the list of local parameters. + * The same parameter cannot be added twice. + *

+ * Implementations of {@code Module} should use this module + * to register any parameters they wish to expose to the user + * if a GUI is in use. + * + * @param newParameters the parameter(s) to add to the list. + */ + protected final void registerParameters(Parameter... newParameters) { + for (int i = 0; i < newParameters.length; i++) { + if (!localParameters.contains(newParameters[i])) { + localParameters.add(newParameters[i]); + } + } + } + + /** + * Sets the name of the module, for GUI display. + * If no name is set, the simple name of the class + * is be used instead. + * + * @param name the name to set. + */ + protected void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } } diff --git a/src/jcgp/backend/modules/es/EvolutionaryStrategy.java b/src/jcgp/backend/modules/es/EvolutionaryStrategy.java index 70e3cd2..1a14552 100644 --- a/src/jcgp/backend/modules/es/EvolutionaryStrategy.java +++ b/src/jcgp/backend/modules/es/EvolutionaryStrategy.java @@ -6,7 +6,7 @@ import jcgp.backend.population.Population; import jcgp.backend.resources.Resources; /** - * This interface specifies the required behaviour of an evolutionary strategy. The evolutionary + * This class specifies the characteristics of an evolutionary strategy. The evolutionary * strategy's job is to generate the next population of solutions. In JCGP this is done by modifying * the provided population object rather than creating a new one. *

@@ -17,8 +17,8 @@ import jcgp.backend.resources.Resources; * argument. *

* Parameters may be specified to control the implemented strategy. Any parameters - * returned by {@code getLocalParameters()} should be displayed by the user interface, - * if it is being used. See {@link Parameter} for more information. + * registered with {@code registerParameters()} should be displayed by the user interface, + * if it is being used. See {@link Module} for more information. *

* It is advisable to use {@code Resources.reportln()} and {@code Resources.report()} * to print any relevant information. Note that reportln() and report() are affected @@ -27,12 +27,11 @@ import jcgp.backend.resources.Resources; * See {@link Resources} for more information. * * @see Module - * * @author Eduardo Pedroni * */ -public interface EvolutionaryStrategy extends Module { - +public abstract class EvolutionaryStrategy extends Module { + /** * Performs the selection algorithm and uses the mutator to create * the next generation of solutions. @@ -42,5 +41,4 @@ public interface EvolutionaryStrategy extends Module { * @param resources parameters and utilities for optional reference. */ public abstract void evolve(Population population, Mutator mutator, Resources resources); - } diff --git a/src/jcgp/backend/modules/es/MuPlusLambda.java b/src/jcgp/backend/modules/es/MuPlusLambda.java index 754e89b..8186b11 100644 --- a/src/jcgp/backend/modules/es/MuPlusLambda.java +++ b/src/jcgp/backend/modules/es/MuPlusLambda.java @@ -1,12 +1,11 @@ package jcgp.backend.modules.es; import jcgp.backend.modules.mutator.Mutator; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.IntegerParameter; +import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.population.Population; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.IntegerParameter; -import jcgp.backend.resources.parameters.Parameter; -import jcgp.backend.resources.parameters.ParameterStatus; /** * (μ + λ)-ES @@ -28,7 +27,7 @@ import jcgp.backend.resources.parameters.ParameterStatus; * @author Eduardo Pedroni * */ -public class MuPlusLambda implements EvolutionaryStrategy { +public class MuPlusLambda extends EvolutionaryStrategy { private IntegerParameter mu, lambda; private BooleanParameter report; @@ -39,6 +38,7 @@ public class MuPlusLambda implements EvolutionaryStrategy { * @param resources a reference to the experiment's resources. */ public MuPlusLambda(final Resources resources) { + super(); mu = new IntegerParameter(1, "Parents (μ)") { @Override public void validate(Number newValue) { @@ -53,6 +53,7 @@ public class MuPlusLambda implements EvolutionaryStrategy { } } }; + lambda = new IntegerParameter(4, "Offspring (λ)") { @Override public void validate(Number newValue) { @@ -67,17 +68,11 @@ public class MuPlusLambda implements EvolutionaryStrategy { } } }; - report = new BooleanParameter(false, "Report") { - @Override - public void validate(Boolean newValue) { - // nothing - } - }; - } - - @Override - public Parameter[] getLocalParameters() { - return new Parameter[] {mu, lambda, report}; + + report = new BooleanParameter(false, "Report"); + + setName("(μ + λ)"); + registerParameters(mu, lambda, report); } @Override @@ -98,9 +93,4 @@ public class MuPlusLambda implements EvolutionaryStrategy { if (report.get()) resources.reportln("[ES] Generation is complete"); } - - @Override - public String toString() { - return "(μ + λ)"; - } } diff --git a/src/jcgp/backend/modules/es/TournamentSelection.java b/src/jcgp/backend/modules/es/TournamentSelection.java index 43fea81..209caca 100644 --- a/src/jcgp/backend/modules/es/TournamentSelection.java +++ b/src/jcgp/backend/modules/es/TournamentSelection.java @@ -3,13 +3,12 @@ package jcgp.backend.modules.es; import java.util.Arrays; import jcgp.backend.modules.mutator.Mutator; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.IntegerParameter; +import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.population.Chromosome; import jcgp.backend.population.Population; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.IntegerParameter; -import jcgp.backend.resources.parameters.Parameter; -import jcgp.backend.resources.parameters.ParameterStatus; /** * Tournament selection @@ -34,7 +33,7 @@ import jcgp.backend.resources.parameters.ParameterStatus; * @author Eduardo Pedroni * */ -public class TournamentSelection implements EvolutionaryStrategy { +public class TournamentSelection extends EvolutionaryStrategy { private IntegerParameter tournamentSize; private BooleanParameter report; @@ -44,7 +43,8 @@ public class TournamentSelection implements EvolutionaryStrategy { * * @param resources a reference to the experiment's resources. */ - public TournamentSelection(final Resources resources) { + public TournamentSelection(final Resources resources) { + super(); tournamentSize = new IntegerParameter(1, "Tournament size") { @Override public void validate(Number newValue) { @@ -65,17 +65,11 @@ public class TournamentSelection implements EvolutionaryStrategy { } } }; - report = new BooleanParameter(false, "Report") { - @Override - public void validate(Boolean newValue) { - // blank - } - }; - } - - @Override - public Parameter[] getLocalParameters() { - return new Parameter[] {tournamentSize, report}; + + report = new BooleanParameter(false, "Report"); + + setName("Tournament selection"); + registerParameters(tournamentSize, report); } @Override @@ -114,9 +108,4 @@ public class TournamentSelection implements EvolutionaryStrategy { if (report.get()) resources.reportln("[ES] Generation is complete"); } - - @Override - public String toString() { - return "Tournament"; - } } diff --git a/src/jcgp/backend/modules/mutator/FixedPointMutator.java b/src/jcgp/backend/modules/mutator/FixedPointMutator.java index 4088918..5d40c57 100644 --- a/src/jcgp/backend/modules/mutator/FixedPointMutator.java +++ b/src/jcgp/backend/modules/mutator/FixedPointMutator.java @@ -1,10 +1,9 @@ package jcgp.backend.modules.mutator; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.IntegerParameter; +import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.IntegerParameter; -import jcgp.backend.resources.parameters.Parameter; -import jcgp.backend.resources.parameters.ParameterStatus; /** * Fixed point mutator @@ -20,16 +19,14 @@ import jcgp.backend.resources.parameters.ParameterStatus; */ public class FixedPointMutator extends PointMutator { - private IntegerParameter geneMutated; - private BooleanParameter report; - /** * Creates a new instance of FixedPointMutator. * * @param resources a reference to the experiment's resources. */ public FixedPointMutator(final Resources resources) { - geneMutated = new IntegerParameter(5, "Genes mutated", false, false) { + super(); + genesMutated = new IntegerParameter(5, "Genes mutated", false, false) { @Override public void validate(Number newValue) { if (newValue.intValue() <= 0) { @@ -43,22 +40,10 @@ public class FixedPointMutator extends PointMutator { } } }; - report = new BooleanParameter(false, "Report") { - @Override - public void validate(Boolean newValue) { - // blank - } - }; + + report = new BooleanParameter(false, "Report"); + + setName("Fixed point mutation"); + registerParameters(genesMutated, report); } - - @Override - public Parameter[] getLocalParameters() { - return new Parameter[] {geneMutated, report}; - } - - @Override - public String toString() { - return "Fixed point mutation"; - } - } diff --git a/src/jcgp/backend/modules/mutator/Mutator.java b/src/jcgp/backend/modules/mutator/Mutator.java index 7435cc1..02fd70a 100644 --- a/src/jcgp/backend/modules/mutator/Mutator.java +++ b/src/jcgp/backend/modules/mutator/Mutator.java @@ -5,13 +5,13 @@ import jcgp.backend.population.Chromosome; import jcgp.backend.resources.Resources; /** - * This interface specifies the required behaviour of a mutation operator. Its job is + * This class specifies the basic characteristics of a mutation operator. Its job is * to modify the connections and functions of the chromosome according to the operator's * parameters. *

* Parameters may be specified to control the implemented mutation. Any parameters - * returned by {@code getLocalParameters()} should be displayed by the user interface, - * if it is being used. See {@link Parameter} for more information. + * registered with {@code registerParameters()} should be displayed by the user interface, + * if it is being used. See {@link Module} for more information. *

* It is advisable to use {@code Resources.reportln()} and {@code Resources.report()} * to print any relevant information. Note that reportln() and report() are affected @@ -24,7 +24,7 @@ import jcgp.backend.resources.Resources; * @author Eduardo Pedroni * */ -public interface Mutator extends Module { +public abstract class Mutator extends Module { /** * Applies mutations to the specified chromosome according @@ -33,6 +33,6 @@ public interface Mutator extends Module { * @param chromosome the chromosome to mutate. * @param resources parameters and utilities for optional reference. */ - void mutate(Chromosome chromosome, Resources resources); + public abstract void mutate(Chromosome chromosome, Resources resources); } diff --git a/src/jcgp/backend/modules/mutator/PercentPointMutator.java b/src/jcgp/backend/modules/mutator/PercentPointMutator.java index 4057027..31a7739 100644 --- a/src/jcgp/backend/modules/mutator/PercentPointMutator.java +++ b/src/jcgp/backend/modules/mutator/PercentPointMutator.java @@ -1,11 +1,10 @@ package jcgp.backend.modules.mutator; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.DoubleParameter; +import jcgp.backend.parameters.ParameterStatus; +import jcgp.backend.parameters.monitors.IntegerMonitor; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.DoubleParameter; -import jcgp.backend.resources.parameters.IntegerParameter; -import jcgp.backend.resources.parameters.Parameter; -import jcgp.backend.resources.parameters.ParameterStatus; /** * Percent point mutator @@ -23,8 +22,6 @@ import jcgp.backend.resources.parameters.ParameterStatus; public class PercentPointMutator extends PointMutator { private DoubleParameter mutationRate; - private IntegerParameter genesMutated; - private BooleanParameter report; /** * Creates a new instance of PointMutator. @@ -32,6 +29,7 @@ public class PercentPointMutator extends PointMutator { * @param resources a reference to the experiment's resources. */ public PercentPointMutator(final Resources resources) { + super(); mutationRate = new DoubleParameter(10, "Percent mutation", false, false) { @Override public void validate(Number newValue) { @@ -47,27 +45,11 @@ public class PercentPointMutator extends PointMutator { } } }; - genesMutated = new IntegerParameter(0, "Genes mutated", true, false) { - @Override - public void validate(Number newValue) { - // blank - } - }; - report = new BooleanParameter(false, "Report") { - @Override - public void validate(Boolean newValue) { - // blank - } - }; - } - - @Override - public Parameter[] getLocalParameters() { - return new Parameter[] {mutationRate, genesMutated, report}; - } - - @Override - public String toString() { - return "Percent point mutation"; + + genesMutated = new IntegerMonitor(0, "Genes mutated"); + report = new BooleanParameter(false, "Report"); + + setName("Percent point mutation"); + registerParameters(mutationRate, genesMutated, report); } } diff --git a/src/jcgp/backend/modules/mutator/PointMutator.java b/src/jcgp/backend/modules/mutator/PointMutator.java index 17c6996..0223a37 100644 --- a/src/jcgp/backend/modules/mutator/PointMutator.java +++ b/src/jcgp/backend/modules/mutator/PointMutator.java @@ -1,12 +1,12 @@ package jcgp.backend.modules.mutator; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.IntegerParameter; import jcgp.backend.population.Chromosome; import jcgp.backend.population.MutableElement; import jcgp.backend.population.Node; import jcgp.backend.population.Output; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.IntegerParameter; /** * Point mutator @@ -20,7 +20,7 @@ import jcgp.backend.resources.parameters.IntegerParameter; * @author Eduardo Pedroni * */ -public abstract class PointMutator implements Mutator { +public abstract class PointMutator extends Mutator { protected IntegerParameter genesMutated; protected BooleanParameter report; diff --git a/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java b/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java index cacb451..c65fc22 100644 --- a/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java +++ b/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java @@ -1,13 +1,12 @@ package jcgp.backend.modules.mutator; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.DoubleParameter; +import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.population.Chromosome; import jcgp.backend.population.Node; import jcgp.backend.population.Output; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.DoubleParameter; -import jcgp.backend.resources.parameters.Parameter; -import jcgp.backend.resources.parameters.ParameterStatus; /** * Probabilistic mutator @@ -22,7 +21,7 @@ import jcgp.backend.resources.parameters.ParameterStatus; * @author Eduardo Pedroni * */ -public class ProbabilisticMutator implements Mutator { +public class ProbabilisticMutator extends Mutator { private DoubleParameter mutationProbability; private BooleanParameter report; @@ -35,6 +34,7 @@ public class ProbabilisticMutator implements Mutator { * @param resources a reference to the experiment's resources. */ public ProbabilisticMutator(Resources resources) { + super(); this.resources = resources; mutationProbability = new DoubleParameter(10, "Mutation probability", false, false) { @@ -48,17 +48,11 @@ public class ProbabilisticMutator implements Mutator { } } }; - report = new BooleanParameter(false, "Report") { - @Override - public void validate(Boolean newValue) { - // blank - } - }; - } - - @Override - public Parameter[] getLocalParameters() { - return new Parameter[] {mutationProbability, report}; + + report = new BooleanParameter(false, "Report"); + + setName("Probabilisic mutation"); + registerParameters(mutationProbability, report); } @Override @@ -113,7 +107,7 @@ public class ProbabilisticMutator implements Mutator { } /** - * This method offers a shorthand to decide whether a mutation should occur or not. + * This method provides a shorthand to decide whether a mutation should occur or not. * A random double is generated in the range 0 <= x < 100 and compared with the * mutation probability parameter. If the generated number is less than the mutation * probability, this returns true meaning a mutation should occur. @@ -123,9 +117,4 @@ public class ProbabilisticMutator implements Mutator { private boolean mutateGene() { return resources.getRandomDouble(100) < mutationProbability.get(); } - - @Override - public String toString() { - return "Probabilistic mutation"; - } } diff --git a/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java b/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java index 099f527..6654928 100644 --- a/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java +++ b/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java @@ -6,12 +6,28 @@ import jcgp.backend.population.Chromosome; import jcgp.backend.population.Population; import jcgp.backend.resources.Resources; +/** + * Digital circuit problem + *

+ * Using this problem type, digital logic circuits can be evolved. + * {@code parseData()} must be used to load the desired circuit + * truth table in the standard CGP .plu format. + * + * @see DigitalCircuitFunctions + * @author Eduardo Pedroni + * + */ public class DigitalCircuitProblem extends TestCaseProblem { + /** + * Construct a new instance of DigitalCircuitProblem. + * + * @param resources a reference to the experiment's resources. + */ public DigitalCircuitProblem(Resources resources) { super(resources); - functionSet = new DigitalCircuitFunctions(); - setProblemName("Symbolic regression"); + setFunctionSet(new DigitalCircuitFunctions()); + setName("Digital circuit"); setFileExtension(".plu"); } @@ -49,17 +65,14 @@ public class DigitalCircuitProblem extends TestCaseProblem { @Override protected double getMaxFitness() { + // calculate the fitness by looking at inputs, not number of test cases double maxFitness = Math.pow(2.0, (double) resources.inputs()) * resources.outputs(); return maxFitness; } - - @Override - public String toString() { - return "Digital circuit"; - } @Override - public void addTestCase(String[] inputs, String[] outputs) { + public TestCase parseTestCase(String[] inputs, String[] outputs) { + // cast the test case values to UnsignedInteger UnsignedInteger[] inputCases = new UnsignedInteger[inputs.length]; UnsignedInteger[] outputCases = new UnsignedInteger[outputs.length]; for (int i = 0; i < inputCases.length; i++) { @@ -69,11 +82,23 @@ public class DigitalCircuitProblem extends TestCaseProblem { outputCases[o] = new UnsignedInteger(outputs[o]); } - addTestCase(new TestCase(inputCases, outputCases)); + return new TestCase(inputCases, outputCases); } @Override public boolean isPerfectSolution(Chromosome fittest) { + // higher fitness is better return fittest.getFitness() >= maxFitness.get(); } + + @Override + public boolean isImprovement(Chromosome fittest) { + // higher fitness is better + if (fittest.getFitness() > bestFitness.get()) { + bestFitness.set(fittest.getFitness()); + return true; + } else { + return false; + } + } } diff --git a/src/jcgp/backend/modules/problem/Problem.java b/src/jcgp/backend/modules/problem/Problem.java index 368d512..721b9b3 100644 --- a/src/jcgp/backend/modules/problem/Problem.java +++ b/src/jcgp/backend/modules/problem/Problem.java @@ -4,6 +4,8 @@ import java.io.File; import jcgp.backend.function.FunctionSet; import jcgp.backend.modules.Module; +import jcgp.backend.parameters.DoubleParameter; +import jcgp.backend.parameters.monitors.DoubleMonitor; import jcgp.backend.population.Chromosome; import jcgp.backend.population.Population; import jcgp.backend.resources.ModifiableResources; @@ -11,11 +13,18 @@ import jcgp.backend.resources.Resources; /** * Defines the general behaviour of a CGP problem. The primary function of Problem - * is to evaluate a population and assign + * is to evaluate a population and assign a fitness value to each chromosome. + *
+ * By convention, the population should be sorted into ascending order of fitness. The + * reason for this is because high fitness is not necessarily better - some problem types + * might treat 0 as the best fitness. In order for the evolutionary strategy to be able to + * pick chromosomes by fitness, the safest way is to sort them such that the last chromosome + * is the fittest. *

- * Parameters may be specified to control the implemented problem. Any parameters - * returned by {@code getLocalParameters()} should be displayed by the user interface, - * if it is being used. See {@link Parameter} for more information. + * When extending this class, the constructor should call a couple methods in order to + * properly construct the problem type: {@code setFunctionSet()} and {@code setFileExtension()}, + * with the respective arguments. As with all subclasses of {@code Module}, {@code setName()} and + * {@code registerParameters()} should be used where appropriate as well. *

* It is advisable to use {@code Resources.reportln()} and {@code Resources.report()} * to print any relevant information. Note that reportln() and report() are affected @@ -24,44 +33,133 @@ import jcgp.backend.resources.Resources; * See {@link Resources} for more information. * * @see Module - * * @author Eduardo Pedroni * */ -public abstract class Problem implements Module { +public abstract class Problem extends Module { - protected FunctionSet functionSet; + private FunctionSet functionSet; private String fileExtension = ".*"; - private String name = this.getClass().getSimpleName(); - public abstract void evaluate(Population population, Resources resources); + protected DoubleParameter maxFitness, bestFitness; - public FunctionSet getFunctionSet() { - return functionSet; + /** + * Initialises the two problem-wide parameters, maxFitness and bestFitness. + */ + public Problem() { + maxFitness = new DoubleMonitor(0, "Max fitness"); + bestFitness = new DoubleMonitor(0, "Best fitness"); + registerParameters(maxFitness, bestFitness); } - public abstract boolean isPerfectSolution(Chromosome fittest); + /** + * The most important method of the problem type. This is called once + * per generation, when the new population has been generated. + *

+ * The basic functionality of this method is to loop through all chromosomes + * in the population and decode them according to the problem type. The + * fitness of each chromosome is then calculated using the problem data + * or otherwise (subjective problem types such as art generation might + * leave fitness evaluations up to the user) and assigned to the appropriate + * chromosome. + *

+ * In addition, realisations of this method should update the value of + * bestFitness as appropriate, since the value of this parameter is displayed + * if a GUI is in use. + * + * @param population the population to be evaluated. + * @param resources parameters and utilities for optional reference. + */ + public abstract void evaluate(Population population, Resources resources); + + /** + * Used to assert whether a given chromosome is a perfect solution + * to the problem. It is up to the problem to define what qualifies + * a perfect solution, as some problems (subject ones such as music and + * art evolution, for example) might not have perfect solutions at all. + *

+ * Note that if this method returns true, the experiment will move on + * to the next run, or finish if no more runs are left. + * + * @param candidate the potentially perfect chromosome. + * @return true if the argument is a perfect solution. + */ + public abstract boolean isPerfectSolution(Chromosome candidate); + /** + * Used to assert whether a given chromosome is an improvement over + * the current best chromosome. A typical implementation of this method + * will simply compare chromosome fitness values, though the problem type + * is free to implement this in any way. + * + * @param candidate the potentially fitter chromosome. + * @return true if the argument is fitter than the currently fittest chromosome. + */ + public abstract boolean isImprovement(Chromosome candidate); + + /** + * Parses the specified file and uses the parsed data to + * set up the problem type instance appropriately. Any necessary + * resource changes can be performed using the provided {@code ModifiableResources} + * instance. + *

+ * In addition, realisations of this method should update the value of + * maxFitness where appropriate, as this may be displayed to the user + * if a GUI is in use. + * + * @param file the data file to parse. + * @param resources a modifiable reference to the experiment's resources. + */ public abstract void parseProblemData(File file, ModifiableResources resources); - public void setFileExtension(String fileExtension) { + /** + * For internal use in subclass constructor, sets the functions to be + * used for this problem type. See {@link FunctionSet} for more details. + * + * @param newFunctionSet the function set to use. + */ + protected void setFunctionSet(FunctionSet newFunctionSet) { + this.functionSet = newFunctionSet; + } + + /** + * @return the FunctionSet object used by this problem type. + */ + public FunctionSet getFunctionSet() { + return functionSet; + } + + /** + * For internal use in subclass constructors, sets the file extension accepted + * by this problem type's parser. This is used by the GUI to filter loaded files + * by extension in a file chooser. File extensions should be set in the form ".*", + * so for plain text files, ".txt" would be used. + * + * @param fileExtension the accepted file extension. + */ + protected void setFileExtension(String fileExtension) { this.fileExtension = fileExtension; } + /** + * @return the file extension accepted by this problem type for problem data files. + */ public String getFileExtension() { return fileExtension; } - public void setProblemName(String newName) { - this.name = newName; - } - - public String getProblemName() { - return name; + /** + * @return the current best fitness, in other words, the fitness + * value of the fittest chromosome in the current generation. + */ + public double getBestFitness() { + return bestFitness.get(); } - @Override - public String toString() { - return name; + /** + * Resets the bestFitness parameter. + */ + public void reset() { + bestFitness.set(0); } } diff --git a/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java b/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java index 5468157..04e9fe8 100644 --- a/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java +++ b/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java @@ -1,24 +1,54 @@ package jcgp.backend.modules.problem; import jcgp.backend.function.SymbolicRegressionFunctions; +import jcgp.backend.parameters.BooleanParameter; +import jcgp.backend.parameters.DoubleParameter; +import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.population.Chromosome; import jcgp.backend.population.Population; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.BooleanParameter; -import jcgp.backend.resources.parameters.DoubleParameter; -import jcgp.backend.resources.parameters.Parameter; -import jcgp.backend.resources.parameters.ParameterStatus; +/** + * Symbolic regression functions + *

+ * Using this problem type, regression problems can be solved. + * {@code parseData()} must be used to load the desired function + * data in the standard CGP .dat format. + *

+ * This problem uses quite a few parameters: + *
    + *
  • Error threshold: the maximum difference allowed between an + * evolved output and the equivalent output from the problem data. + * Outputs within the error threshold will be considered correct. + * This is only used if HITS is enabled.
  • + *
  • Perfection threshold: if the fitness is calculated without + * using the HITS method, it is a decimal value. A solution is + * considered perfect when the difference between its fitness and + * the maximum possible fitness is within the perfection threshold.
  • + *
  • HITS-based fitness: increment the fitness by 1 whenever the + * chromosome output is within the error threshold.
+ * + * + * @see SymbolicRegressionFunctions + * @author Eduardo Pedroni + * + */ public class SymbolicRegressionProblem extends TestCaseProblem { private DoubleParameter errorThreshold, perfectionThreshold; private BooleanParameter hitsBasedFitness; - + + /** + * Creates a new instance of SymbolicRegressionProblem. + * + * @param resources a reference to the experiment's resources. + */ public SymbolicRegressionProblem(Resources resources) { super(resources); - functionSet = new SymbolicRegressionFunctions(); - setProblemName("Symbolic regression"); + setFunctionSet(new SymbolicRegressionFunctions()); + setName("Symbolic regression"); setFileExtension(".dat"); + errorThreshold = new DoubleParameter(0.01, "Error threshold") { @Override public void validate(Number newValue) { @@ -33,6 +63,7 @@ public class SymbolicRegressionProblem extends TestCaseProblem { } } }; + perfectionThreshold = new DoubleParameter(0.000001, "Perfection threshold") { @Override public void validate(Number newValue) { @@ -47,12 +78,10 @@ public class SymbolicRegressionProblem extends TestCaseProblem { } } }; - hitsBasedFitness = new BooleanParameter(true, "HITS-based fitness") { - @Override - public void validate(Boolean newValue) { - // blank - } - }; + + hitsBasedFitness = new BooleanParameter(true, "HITS-based fitness"); + + registerParameters(errorThreshold, perfectionThreshold, hitsBasedFitness); } @Override @@ -75,7 +104,6 @@ public class SymbolicRegressionProblem extends TestCaseProblem { } else { fitness += 1 - Math.abs(cgpValue - dataValue); } - } } // assign the resulting fitness to the respective individual @@ -87,7 +115,8 @@ public class SymbolicRegressionProblem extends TestCaseProblem { } @Override - public void addTestCase(String[] inputs, String[] outputs) { + public TestCase parseTestCase(String[] inputs, String[] outputs) { + // cast the test case values to UnsignedInteger Double[] inputCases = new Double[inputs.length]; Double[] outputCases = new Double[outputs.length]; for (int i = 0; i < inputCases.length; i++) { @@ -97,16 +126,23 @@ public class SymbolicRegressionProblem extends TestCaseProblem { outputCases[o] = Double.parseDouble(outputs[o]); } - addTestCase(new TestCase(inputCases, outputCases)); + return new TestCase(inputCases, outputCases); } @Override public boolean isPerfectSolution(Chromosome fittest) { + // higher fitness is better return fittest.getFitness() >= maxFitness.get() - perfectionThreshold.get(); } @Override - public Parameter[] getLocalParameters() { - return new Parameter[]{maxFitness, errorThreshold, perfectionThreshold, hitsBasedFitness}; + public boolean isImprovement(Chromosome fittest) { + // higher fitness is better + if (fittest.getFitness() > bestFitness.get()) { + bestFitness.set(fittest.getFitness()); + return true; + } else { + return false; + } } } diff --git a/src/jcgp/backend/modules/problem/TestCaseProblem.java b/src/jcgp/backend/modules/problem/TestCaseProblem.java index 7ce0327..c11fab4 100644 --- a/src/jcgp/backend/modules/problem/TestCaseProblem.java +++ b/src/jcgp/backend/modules/problem/TestCaseProblem.java @@ -1,100 +1,144 @@ package jcgp.backend.modules.problem; import java.io.File; -import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import jcgp.backend.parsers.TestCaseParser; import jcgp.backend.resources.ModifiableResources; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.DoubleParameter; -import jcgp.backend.resources.parameters.Parameter; /** + * Abstract model for a problem that uses test cases. A test case + * problem is any problem that compares the chromosome output to + * an expected output taken from a table of input-output mappings. + *

+ * This class defines a basic data type for storing test cases, + * TestCase, and provides core functionality to add and manipulate + * test cases in the problem. A subclass of {@code TestCaseProblem} + * must simply override {@code parseTestCase()} to convert parsed + * problem data strings into the required data type (T). * - * This fitness function module implements a simple test case evaluator. - * - * A TestCase object is a - * - * + * @see Problem * @author Eduardo Pedroni - * + * @param the data type to be used by the TestCaseProblem. */ -public abstract class TestCaseProblem extends Problem { +public abstract class TestCaseProblem extends Problem { - public static class TestCase { - private T[] inputs; - private T[] outputs; + /** + * Basic data type for encapsulating test cases, it simply + * contains arrays of inputs and outputs and associated getters. + * + * @author Eduardo Pedroni + * @param + */ + public static class TestCase { + private U[] inputs; + private U[] outputs; - public TestCase(T[] inputs, T[] outputs) { + /** + * Creates a new test case, inputs and outputs + * must be specified upon instantiation. + * + * @param inputs the array of inputs. + * @param outputs the array of outputs. + */ + public TestCase(U[] inputs, U[] outputs) { this.inputs = inputs; this.outputs = outputs; } - public T getInput(int index) { + /** + * @param index the index to return. + * @return the indexed input. + */ + public U getInput(int index) { return inputs[index]; } - public T getOutput(int index) { + /** + * @param index the index to return. + * @return the indexed output. + */ + public U getOutput(int index) { return outputs[index]; } - public T[] getInputs() { + /** + * @return the complete array of inputs. + */ + public U[] getInputs() { return inputs; } - public T[] getOutputs() { + /** + * @return the complete array of outputs. + */ + public U[] getOutputs() { return outputs; } } - protected ObservableList> testCases; - protected DoubleParameter maxFitness; + protected ObservableList> testCases; protected Resources resources; + /** + * Creates a new TestCaseProblem object. + * + * @param resources a reference to the experiment's resources. + */ public TestCaseProblem(Resources resources) { super(); - this.resources = resources; - - maxFitness = new DoubleParameter(0, "Max fitness", true, false) { - @Override - public void validate(Number newValue) { - // blank - } - }; testCases = FXCollections.observableArrayList(); } - @Override - public Parameter[] getLocalParameters() { - return new Parameter[]{maxFitness}; - } - + /** + * For internal use only, this method computes and returns the maximum fitness + * based on the number of test cases. Subclasses should override this method + * as necessary. + * + * @return the maximum fitness based on number of test cases. + */ protected double getMaxFitness() { int fitness = 0; - - for (TestCase tc : testCases) { + for (TestCase tc : testCases) { fitness += tc.getOutputs().length; } - return fitness; } - public void setTestCases(List> testCases) { - this.testCases.clear(); - this.testCases.addAll(testCases); - maxFitness.set(getMaxFitness()); - } - - public ObservableList> getTestCases() { + /** + * @return a list containing the test cases. + */ + public ObservableList> getTestCases() { return testCases; } - public abstract void addTestCase(String[] inputs, String[] outputs); + /** + * This method is used internally by {@code addTestCase()} in order + * to appropriately parse strings into the right data type for the + * test cases. Since the data type is problem-dependent, subclasses must + * implement this method. This method must return a built {@code TestCase} + * object from the arguments given. + * + * @param inputs the inputs represented as strings. + * @param outputs the outputs represented as strings. + * @return the parsed test case. + */ + protected abstract TestCase parseTestCase(String[] inputs, String[] outputs); - protected final void addTestCase(TestCase testCase) { + /** + * Adds test cases to the problem instance as they get parsed from a + * problem data file. This template method uses {@code parseTestCase}, which + * must be implemented by subclasses. + * + * @param inputs the inputs represented as strings. + * @param outputs the outputs represented as strings. + */ + public final void addTestCase(String[] inputs, String[] outputs) { + TestCase testCase = parseTestCase(inputs, outputs); + if (testCase.getInputs().length != resources.inputs()) { throw new IllegalArgumentException("Received test case with " + testCase.getInputs().length + " inputs but need exactly " + resources.inputs()); @@ -106,20 +150,18 @@ public abstract class TestCaseProblem extends Problem { maxFitness.set(getMaxFitness()); } } - - public int getInputCount() { - return resources.inputs(); - } - - public int getOutputCount() { - return resources.outputs(); - } + /** + * Remove all test cases. + */ public void clearTestCases() { testCases.clear(); + maxFitness.set(getMaxFitness()); } + @Override public void parseProblemData(File file, ModifiableResources resources) { + // use standard test case parser for this TestCaseParser.parse(file, this, resources); } } diff --git a/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java b/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java index 8363ef8..94417ae 100644 --- a/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java +++ b/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java @@ -7,26 +7,34 @@ import jcgp.backend.population.Chromosome; import jcgp.backend.population.Population; import jcgp.backend.resources.ModifiableResources; import jcgp.backend.resources.Resources; -import jcgp.backend.resources.parameters.Parameter; +/** + * Travelling salesman problem + *

+ * Using this problem type, travelling salesman tours can be evolved. + * {@code parseData()} must be used to load the desired city + * coordinates in the standard .tsp format. + * + * @see TravellingSalesmanFunctions + * @author Eduardo Pedroni + * + */ public class TravellingSalesmanProblem extends Problem { + /** + * Construct a new instance of TravellingSalesmanProblem. + * + * @param resources a reference to the experiment's resources. + */ public TravellingSalesmanProblem(Resources resources) { - functionSet = new TravellingSalesmanFunctions(); - setProblemName("Travelling salesman"); + setFunctionSet(new TravellingSalesmanFunctions()); + setName("Travelling salesman"); setFileExtension(".tsp"); } - @Override - public Parameter[] getLocalParameters() { - // TODO Auto-generated method stub - return null; - } - @Override public void evaluate(Population population, Resources resources) { // TODO Auto-generated method stub - } @Override @@ -38,6 +46,11 @@ public class TravellingSalesmanProblem extends Problem { @Override public void parseProblemData(File file, ModifiableResources resources) { // TODO Auto-generated method stub - + } + + @Override + public boolean isImprovement(Chromosome fittest) { + // TODO Auto-generated method stub + return false; } } -- cgit v1.2.3