aboutsummaryrefslogtreecommitdiffstats
path: root/src/jcgp/backend/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/jcgp/backend/modules')
-rw-r--r--src/jcgp/backend/modules/Module.java67
-rw-r--r--src/jcgp/backend/modules/es/EvolutionaryStrategy.java12
-rw-r--r--src/jcgp/backend/modules/es/MuPlusLambda.java32
-rw-r--r--src/jcgp/backend/modules/es/TournamentSelection.java33
-rw-r--r--src/jcgp/backend/modules/mutator/FixedPointMutator.java35
-rw-r--r--src/jcgp/backend/modules/mutator/Mutator.java10
-rw-r--r--src/jcgp/backend/modules/mutator/PercentPointMutator.java40
-rw-r--r--src/jcgp/backend/modules/mutator/PointMutator.java6
-rw-r--r--src/jcgp/backend/modules/mutator/ProbabilisticMutator.java33
-rw-r--r--src/jcgp/backend/modules/problem/DigitalCircuitProblem.java43
-rw-r--r--src/jcgp/backend/modules/problem/Problem.java142
-rw-r--r--src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java72
-rw-r--r--src/jcgp/backend/modules/problem/TestCaseProblem.java148
-rw-r--r--src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java35
14 files changed, 453 insertions, 255 deletions
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.
+ * <br>
+ * 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<Parameter<?>> localParameters = new ArrayList<Parameter<?>>();
+ 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<Parameter<?>> getLocalParameters() {
+ return localParameters;
+ }
+
+ /**
+ * Adds one or more parameters to the list of local parameters.
+ * The same parameter cannot be added twice.
+ * <br><br>
+ * 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.
* <br><br>
@@ -17,8 +17,8 @@ import jcgp.backend.resources.Resources;
* argument.
* <br><br>
* 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.
* <br><br>
* 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.
* <br><br>
* 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.
* <br><br>
* 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
+ * <br><br>
+ * 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<UnsignedInteger> {
+ /**
+ * 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<UnsignedInteger> {
@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<UnsignedInteger> 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<UnsignedInteger> {
outputCases[o] = new UnsignedInteger(outputs[o]);
}
- addTestCase(new TestCase<UnsignedInteger>(inputCases, outputCases));
+ return new TestCase<UnsignedInteger>(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.
+ * <br>
+ * 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.
* <br><br>
- * 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.
* <br><br>
* 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.
+ * <br><br>
+ * 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.
+ * <br><br>
+ * 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.
+ * <br><br>
+ * 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.
+ * <br><br>
+ * 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
+ * <br><br>
+ * 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.
+ * <br><br>
+ * This problem uses quite a few parameters:
+ * <ul>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>HITS-based fitness: increment the fitness by 1 whenever the
+ * chromosome output is within the error threshold.</li></ul>
+ *
+ *
+ * @see SymbolicRegressionFunctions
+ * @author Eduardo Pedroni
+ *
+ */
public class SymbolicRegressionProblem extends TestCaseProblem<Double> {
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<Double> {
}
}
};
+
perfectionThreshold = new DoubleParameter(0.000001, "Perfection threshold") {
@Override
public void validate(Number newValue) {
@@ -47,12 +78,10 @@ public class SymbolicRegressionProblem extends TestCaseProblem<Double> {
}
}
};
- 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<Double> {
} else {
fitness += 1 - Math.abs(cgpValue - dataValue);
}
-
}
}
// assign the resulting fitness to the respective individual
@@ -87,7 +115,8 @@ public class SymbolicRegressionProblem extends TestCaseProblem<Double> {
}
@Override
- public void addTestCase(String[] inputs, String[] outputs) {
+ public TestCase<Double> 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<Double> {
outputCases[o] = Double.parseDouble(outputs[o]);
}
- addTestCase(new TestCase<Double>(inputCases, outputCases));
+ return new TestCase<Double>(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.
+ * <br><br>
+ * 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 <T> the data type to be used by the TestCaseProblem.
*/
-public abstract class TestCaseProblem<U extends Object> extends Problem {
+public abstract class TestCaseProblem<T> extends Problem {
- public static class TestCase<T> {
- 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 <U>
+ */
+ public static class TestCase<U> {
+ 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<TestCase<U>> testCases;
- protected DoubleParameter maxFitness;
+ protected ObservableList<TestCase<T>> 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<U> tc : testCases) {
+ for (TestCase<T> tc : testCases) {
fitness += tc.getOutputs().length;
}
-
return fitness;
}
- public void setTestCases(List<TestCase<U>> testCases) {
- this.testCases.clear();
- this.testCases.addAll(testCases);
- maxFitness.set(getMaxFitness());
- }
-
- public ObservableList<TestCase<U>> getTestCases() {
+ /**
+ * @return a list containing the test cases.
+ */
+ public ObservableList<TestCase<T>> 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<T> parseTestCase(String[] inputs, String[] outputs);
- protected final void addTestCase(TestCase<U> 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<T> 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<U extends Object> 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
+ * <br><br>
+ * 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;
}
}