aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.classpath4
-rw-r--r--src/jcgp/JCGP.java421
-rw-r--r--src/jcgp/backend/function/DigitalCircuitFunctions.java85
-rw-r--r--src/jcgp/backend/function/Function.java6
-rw-r--r--src/jcgp/backend/function/FunctionSet.java64
-rw-r--r--src/jcgp/backend/function/SymbolicRegressionFunctions.java21
-rw-r--r--src/jcgp/backend/function/TravellingSalesmanFunctions.java19
-rw-r--r--src/jcgp/backend/function/UnsignedInteger.java20
-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
-rw-r--r--src/jcgp/backend/parameters/BooleanParameter.java79
-rw-r--r--src/jcgp/backend/parameters/DoubleParameter.java79
-rw-r--r--src/jcgp/backend/parameters/IntegerParameter.java79
-rw-r--r--src/jcgp/backend/parameters/Parameter.java128
-rw-r--r--src/jcgp/backend/parameters/ParameterStatus.java53
-rw-r--r--src/jcgp/backend/parameters/monitors/BooleanMonitor.java44
-rw-r--r--src/jcgp/backend/parameters/monitors/DoubleMonitor.java45
-rw-r--r--src/jcgp/backend/parameters/monitors/IntegerMonitor.java44
-rw-r--r--src/jcgp/backend/resources/Console.java31
-rw-r--r--src/jcgp/backend/resources/ModifiableResources.java143
-rw-r--r--src/jcgp/backend/resources/Resources.java161
-rw-r--r--src/jcgp/backend/resources/parameters/BooleanParameter.java23
-rw-r--r--src/jcgp/backend/resources/parameters/DoubleParameter.java21
-rw-r--r--src/jcgp/backend/resources/parameters/IntegerParameter.java21
-rw-r--r--src/jcgp/backend/resources/parameters/Parameter.java57
-rw-r--r--src/jcgp/backend/resources/parameters/ParameterStatus.java16
-rw-r--r--src/jcgp/backend/statistics/RunEntry.java62
-rw-r--r--src/jcgp/backend/statistics/StatisticsLogger.java233
-rw-r--r--src/jcgp/backend/tests/TestFunctionSet.java13
-rw-r--r--src/jcgp/gui/GUI.java7
-rw-r--r--src/jcgp/gui/settings/SettingsPane.java13
-rw-r--r--src/jcgp/gui/settings/parameters/GUIBooleanParameter.java4
-rw-r--r--src/jcgp/gui/settings/parameters/GUIDoubleParameter.java4
-rw-r--r--src/jcgp/gui/settings/parameters/GUIIntegerParameter.java4
-rw-r--r--src/jcgp/gui/settings/parameters/GUIParameter.java28
-rw-r--r--src/jcgp/gui/settings/testcase/TestCaseTable.java15
48 files changed, 2100 insertions, 655 deletions
diff --git a/.classpath b/.classpath
index 3e0fb27..591a691 100644
--- a/.classpath
+++ b/.classpath
@@ -3,5 +3,9 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+ <classpathentry kind="lib" path="/home/eddy/project/development/res/reflections-0.9.9-RC1-uberjar.jar"/>
+ <classpathentry kind="lib" path="/home/eddy/project/development/res/guava-17.0.jar"/>
+ <classpathentry kind="lib" path="/home/eddy/project/development/res/slf4j-1.7.7/slf4j-api-1.7.7.jar"/>
+ <classpathentry kind="lib" path="/home/eddy/project/development/res/javassist-3.18.0-GA/javassist.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/src/jcgp/JCGP.java b/src/jcgp/JCGP.java
index f36ac7c..be09b4a 100644
--- a/src/jcgp/JCGP.java
+++ b/src/jcgp/JCGP.java
@@ -1,19 +1,14 @@
package jcgp;
import java.io.File;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Set;
import jcgp.backend.modules.es.EvolutionaryStrategy;
-import jcgp.backend.modules.es.MuPlusLambda;
-import jcgp.backend.modules.es.TournamentSelection;
-import jcgp.backend.modules.mutator.FixedPointMutator;
import jcgp.backend.modules.mutator.Mutator;
-import jcgp.backend.modules.mutator.PercentPointMutator;
-import jcgp.backend.modules.mutator.ProbabilisticMutator;
-import jcgp.backend.modules.problem.DigitalCircuitProblem;
import jcgp.backend.modules.problem.Problem;
-import jcgp.backend.modules.problem.SymbolicRegressionProblem;
import jcgp.backend.modules.problem.TestCaseProblem;
-import jcgp.backend.modules.problem.TravellingSalesmanProblem;
import jcgp.backend.parsers.ChromosomeParser;
import jcgp.backend.parsers.FunctionParser;
import jcgp.backend.parsers.ParameterParser;
@@ -22,103 +17,253 @@ import jcgp.backend.population.Population;
import jcgp.backend.resources.Console;
import jcgp.backend.resources.ModifiableResources;
import jcgp.backend.resources.Resources;
+import jcgp.backend.statistics.StatisticsLogger;
+
+import org.reflections.Reflections;
/**
*
- * Top-level CGP class. This class is the entry point for a CGP experiment.
- * <br>
- * An instance of JCGP encapsulates the entire experiment. It contains a Resources
+ * Top-level JCGP class. This class is the entry point for a CGP experiment.
+ * <br><br>
+ * An instance of JCGP encapsulates the entire experiment. It contains a {@code Resources}
* object which can be retrieved via a getter. Modules can be selected using their
- * respective setters and function sets can be selected through the resources.
- *
- * The flow of the experiment is controlled using start() and nextGeneration(). The
- * experiment can be reset with reset(), TODO comment
- *
+ * respective setters.
+ * <br><br>
+ * The flow of the experiment is controlled using {@code start()}, {@code nextGeneration()}
+ * and {@code reset()}. Files can be loaded with their respective load methods and
+ * chromosome configurations can be saved with {@code saveChromosome()}.
+ * <br><br>
+ * JCGP supports an extra console in addition to {@code System.console()}, so that messages
+ * can also be printed to a GUI, for example. This extra console can be set with {@code setConsole()},
+ * and must implement jcgp.resources.Console.
*
* @author Eduardo Pedroni
- * @see Resources, Module, FunctionSet
+ * @see Resources, Module
*/
public class JCGP {
- // make resources
private final ModifiableResources resources = new ModifiableResources();
/*
* The following arrays contain all available modules. These collections are read by the GUI
- * when generating menus, so modules not added here will *NOT* be selectable in the GUI.
+ * when generating menus and are populated automatically using reflection.
*
* Each array is accompanied by a field which contains a reference to the currently selected
* module, 0 by default.
*/
// mutators
- private Mutator[] mutators = new Mutator[] {
- new PercentPointMutator(resources),
- new FixedPointMutator(resources),
- new ProbabilisticMutator(resources)};
- private Mutator mutator = mutators[0];
+ private ArrayList<Mutator> mutators;
+ private Mutator mutator;
// evolutionary algorithms
- private EvolutionaryStrategy[] evolutionaryStrategies = new EvolutionaryStrategy[] {
- new MuPlusLambda(resources),
- new TournamentSelection(resources)};
- private EvolutionaryStrategy evolutionaryStrategy = evolutionaryStrategies[0];
+ private ArrayList<EvolutionaryStrategy> evolutionaryStrategies;
+ private EvolutionaryStrategy evolutionaryStrategy;
// problem types
- private Problem[] problems = new Problem[] {
- new SymbolicRegressionProblem(resources),
- new DigitalCircuitProblem(resources),
- new TravellingSalesmanProblem(resources)};
- private Problem problem = problems[0];
+ private ArrayList<Problem> problems;
+ private Problem problem;
- /*
- * the population of chromosomes
- */
private Population population;
+
+ private StatisticsLogger statistics = new StatisticsLogger();
+ private Reflections reflections;
+
+ // these record the best results found in the run, in case the runs ends before a perfect solution is found
+ private int lastImprovementGeneration = 0, activeNodes = 0;
+ private double bestFitnessFound = 0;
+
private boolean finished = false;
/**
- * TODO comment this!
+ * JCGP main method, this is used to execute JCGP from the command line.
+ * <br><br>
+ * In this case the program works in the same way as the classic CGP implementation,
+ * requiring a .par file and an optional problem data file. As in the traditional CGP
+ * implementation, the program must be compiled with the right problem type selected.
*
- * @param args
+ * @param args one or more files needed to perform the experiment.
*/
public static void main(String... args) {
+ // check that files have been provided
if (args.length < 1) {
System.err.println("JCGP requires at least a .par file.");
System.exit(1);
}
+ // prepare experiment
JCGP jcgp = new JCGP();
jcgp.loadParameters(new File(args[0]));
if (jcgp.getProblem() instanceof TestCaseProblem) {
TestCaseParser.parse(new File(args[2]), (TestCaseProblem<?>) jcgp.getProblem(), jcgp.getResources());
}
-
+ // kick it off
jcgp.start();
}
+
+ /**
+ * Creates a new instance of JCGP.
+ */
public JCGP() {
- resources.setFunctionSet(problem.getFunctionSet());
+ // prepare reflections instance
+ reflections = new Reflections("jcgp");
+
+ // generate module lists from all encountered valid modules
+ createEvolutionaryStrategyList();
+ createMutatorList();
+ createProblemList();
+
+ // create a new population
population = new Population(resources);
}
+ /**
+ * Iterates through all classes in the classpath and builds a list
+ * of instances of all valid evolutionary strategies found. In order
+ * to be included in this list, a class must implement {@code EvolutionaryStrategy}
+ * and must contain a constructor with a Resources object as its only argument.
+ */
+ private void createEvolutionaryStrategyList() {
+ Set<Class<? extends EvolutionaryStrategy>> esList = reflections.getSubTypesOf(EvolutionaryStrategy.class);
+ evolutionaryStrategies = new ArrayList<EvolutionaryStrategy>();
+
+ // go through all subclasses of EvolutionaryStrategy
+ for (Class<? extends EvolutionaryStrategy> esType : esList) {
+ Constructor<? extends EvolutionaryStrategy> constructor;
+ try {
+ constructor = esType.getConstructor(Resources.class);
+ } catch (NoSuchMethodException | SecurityException e) {
+ // constructor most likely doesnt exist, try next class
+ System.out.println("Warning: could not find valid constructor for " + esType.getName() + ", skipping...");
+ continue;
+ }
+
+ if (constructor != null) {
+ EvolutionaryStrategy es;
+ try {
+ es = (EvolutionaryStrategy) constructor.newInstance(resources);
+ } catch (Exception e) {
+ // could not instantiate for some reason, keep going
+ System.out.println("Warning: could not instantiate " + esType.getName() + ", skipping...");
+ continue;
+ }
+ // add it to the list if it was instantiated properly
+ evolutionaryStrategies.add(es);
+ }
+ }
+ // choose the initial module
+ evolutionaryStrategy = evolutionaryStrategies.get(0);
+ }
+
+ /**
+ * Iterates through all classes in the classpath and builds a list
+ * of instances of all valid mutators found. In order to be included
+ * in this list, a class must implement {@code Mutator}
+ * and must contain a constructor with a Resources object as its only argument.
+ */
+ private void createMutatorList() {
+ Set<Class<? extends Mutator>> mutatorList = reflections.getSubTypesOf(Mutator.class);
+ mutators = new ArrayList<Mutator>();
+
+ // go through all subclasses of Mutator
+ for (Class<? extends Mutator> mutatorType : mutatorList) {
+ Constructor<? extends Mutator> constructor;
+ try {
+ constructor = mutatorType.getConstructor(Resources.class);
+ } catch (NoSuchMethodException | SecurityException e) {
+ // constructor most likely doesnt exist, try next class
+ System.out.println("Warning: could not find valid constructor for "
+ + mutatorType.getName() + ", skipping...");
+ continue;
+ }
+
+ if (constructor != null) {
+ Mutator mutator;
+ try {
+ mutator = (Mutator) constructor.newInstance(resources);
+ } catch (Exception e) {
+ // could not instantiate for some reason, keep going
+ System.out.println("Warning: could not instantiate " + mutatorType.getName() + ", skipping...");
+ continue;
+ }
+ // add it to the list if it was instantiated properly
+ mutators.add(mutator);
+ }
+ }
+ // choose the initial module
+ mutator = mutators.get(0);
+ }
+
+ /**
+ * Iterates through all classes in the classpath and builds a list
+ * of instances of all valid problem types found. In order to be included
+ * in this list, a class must implement {@code Problem}
+ * and must contain a constructor with a Resources object as its only argument.
+ */
+ private void createProblemList() {
+ Set<Class<? extends Problem>> problemTypes = reflections.getSubTypesOf(Problem.class);
+ problems = new ArrayList<Problem>();
+
+ // go through all subclasses of Problem
+ for (Class<? extends Problem> problemType : problemTypes) {
+
+ Constructor<? extends Problem> constructor;
+ try {
+ constructor = problemType.getConstructor(Resources.class);
+ } catch (NoSuchMethodException | SecurityException e) {
+ // constructor most likely doesnt exist, try next class
+ System.out.println("Warning: could not find valid constructor for "
+ + problemType.getName() + ", skipping...");
+ continue;
+ }
+
+ if (constructor != null) {
+ Problem problem;
+ try {
+ problem = (Problem) constructor.newInstance(resources);
+ } catch (Exception e) {
+ // could not instantiate for some reason, keep going
+ System.out.println("Warning: could not instantiate " + problemType.getName() + ", skipping...");
+ continue;
+ }
+ // add it to the list if it was instantiated properly
+ problems.add(problem);
+ }
+ }
+ // choose the initial module
+ problem = problems.get(0);
+ resources.setFunctionSet(problem.getFunctionSet());
+ }
+
+ /**
+ * Returns a reference to the ModifiableResources used by the
+ * experiment. <br>
+ * Use this with care, since changing experiment parameters may
+ * have unintended effects if not done properly.
+ *
+ * @return a reference to the experiment's resources.
+ */
public ModifiableResources getResources() {
return resources;
}
+ /**
+ * @return a reference to the experiment's population.
+ */
public Population getPopulation() {
return population;
}
/**
- * @return the mutators
+ * @return a complete list of the experiment's mutators.
*/
- public Mutator[] getMutators() {
+ public ArrayList<Mutator> getMutators() {
return mutators;
}
/**
- * @return the mutator
+ * @return the currently selected mutator.
*/
public Mutator getMutator() {
return mutator;
@@ -126,15 +271,15 @@ public class JCGP {
/**
- * @return the evolutionaryAlgorithms
+ * @return a complete list of the experiment's evolutionary strategies.
*/
- public EvolutionaryStrategy[] getEvolutionaryStrategies() {
+ public ArrayList<EvolutionaryStrategy> getEvolutionaryStrategies() {
return evolutionaryStrategies;
}
/**
- * @return the evolutionaryAlgorithm
+ * @return the currently selected evolutionary strategy.
*/
public EvolutionaryStrategy getEvolutionaryStrategy() {
return evolutionaryStrategy;
@@ -142,15 +287,15 @@ public class JCGP {
/**
- * @return the fitnessFunctions
+ * @return a complete list of the experiment's problem types.
*/
- public Problem[] getProblems() {
+ public ArrayList<Problem> getProblems() {
return problems;
}
/**
- * @return the fitnessFunction
+ * @return the currently selected problem type.
*/
public Problem getProblem() {
return problem;
@@ -158,83 +303,163 @@ public class JCGP {
/**
- * @param mutator the mutator to set
+ * @param mutator the index of the desired mutator.
*/
public void setMutator(int index) {
- this.mutator = mutators[index];
+ this.mutator = mutators.get(index);
resources.println("[CGP] Mutator selected: " + mutator.toString());
}
/**
- * @param evolutionaryStrategy the evolutionaryAlgorithm to set
+ * @param evolutionaryStrategy the index of the desired evolutionary strategy.
*/
public void setEvolutionaryStrategy(int index) {
- this.evolutionaryStrategy = evolutionaryStrategies[index];
+ this.evolutionaryStrategy = evolutionaryStrategies.get(index);
resources.println("[CGP] Evolutionary strategy selected: " + evolutionaryStrategy.toString());
}
/**
- * @param problem the fitnessFunction to set
+ * @param problem the index of the desired problem type.
*/
public void setProblem(int index) {
- this.problem = problems[index];
+ this.problem = problems.get(index);
resources.setFunctionSet(problem.getFunctionSet());
}
+ /**
+ * Performs one full generational cycle. More specifically,
+ * this method evaluates the current population using the
+ * selected problem, and checks whether a solution has been found.
+ * <br>
+ * If the experiment is to continue, a new generation is created
+ * using the selected evolutionary strategy and mutator.
+ * <br><br>
+ * This method also deals with ending runs, in other words,
+ * a new population is created at the end of each run automatically.
+ * When all runs have been performed, this method sets the experiment
+ * finished flag and does nothing until {@code reset()} is called.
+ */
public void nextGeneration() {
if (!finished) {
problem.evaluate(population, (Resources) resources);
- reportGeneration();
if (resources.currentGeneration() < resources.generations()) {
+
// we still have generations left to go
if (problem.isPerfectSolution(population.getFittest())) {
+ // log results
+ statistics.logRun(resources.currentGeneration(), population.getFittest().getFitness(), population.getFittest().getActiveNodes().size(), true);
+ resetStatisticsValues();
+
// solution has been found, start next run
- resources.println("[CGP] Solution found, generation " + resources.currentGeneration() + ", chromosome " + population.getFittestIndex());
+ resources.println("[CGP] Solution found, generation " + resources.currentGeneration() + ", chromosome " + population.getFittestIndex() + "\n");
if (resources.currentRun() < resources.runs()) {
+
// there are still runs left
- resources.setCurrentRun(resources.currentRun() + 1);
- resources.setCurrentGeneration(0);
+ resources.incrementRun();
+ resources.setCurrentGeneration(1);
// start a new population
population.reinitialise();
} else {
// no more generations and no more runs, we're done
+ printStatistics();
finished = true;
}
} else {
- resources.setCurrentGeneration(resources.currentGeneration() + 1);
+ if (problem.isImprovement(population.getFittest())) {
+ printImprovement();
+ lastImprovementGeneration = resources.currentGeneration();
+ bestFitnessFound = population.getFittest().getFitness();
+ activeNodes = population.getFittest().getActiveNodes().size();
+ } else {
+ reportGeneration();
+ }
+ resources.incrementGeneration();
// solution isn't perfect and we still have generations left, evolve more!
evolutionaryStrategy.evolve(population, mutator, (Resources) resources);
}
} else {
- // the run has ended, check if any more runs must be done
- resources.println("[CGP] Solution not found, highest fitness achieved was "
- + population.getFittest().getFitness()
- + " by chromosome " + population.getFittestIndex());
-
+ // the run has ended, tell the user and log it
+ resources.println("[CGP] Solution not found, best fitness achieved was "
+ + bestFitnessFound + "\n");
+
+ statistics.logRun(lastImprovementGeneration, bestFitnessFound, activeNodes, false);
+ resetStatisticsValues();
+
+ // check if any more runs must be done
if (resources.currentRun() < resources.runs()) {
// the run has ended but there are still runs left
- resources.setCurrentRun(resources.currentRun() + 1);
- resources.setCurrentGeneration(0);
+ resources.incrementRun();
+ resources.setCurrentGeneration(1);
// start a new population
population.reinitialise();
} else {
// no more generations and no more runs, we're done
+ printStatistics();
finished = true;
}
}
}
}
+ /**
+ * Used internally for printing statistics at the end of the experiment.
+ * This method currently prints the exact same statistics as the ones
+ * provided by the classic CGP implementation.
+ */
+ private void printStatistics() {
+ resources.println("[CGP] Experiment finished");
+ resources.println("[CGP] Average fitness: " + statistics.getAverageFitness());
+ resources.println("[CGP] Std dev fitness: " + statistics.getAverageFitnessStdDev());
+
+ resources.println("[CGP] Average number of active nodes: " + statistics.getAverageActiveNodes());
+ resources.println("[CGP] Std dev number of active nodes: " + statistics.getAverageActiveNodesStdDev());
+
+ resources.println("[CGP] Average best generation: " + statistics.getAverageGenerations());
+ resources.println("[CGP] Std dev best generation: " + statistics.getAverageGenerationsStdDev());
+
+ resources.println("[CGP] Highest fitness of all runs: " + statistics.getHighestFitness());
+ resources.println("[CGP] Lowest fitness of all runs: " + statistics.getLowestFitness());
+
+ resources.println("[CGP] Perfect solutions: " + statistics.getSuccessfulRuns());
+ resources.println("[CGP] Success rate: " + (statistics.getSuccessRate() * 100) + "%");
+
+ resources.println("[CGP] Average generations for perfect solutions only: " + statistics.getAverageSuccessfulGenerations());
+ resources.println("[CGP] Std dev generations for perfect solutions only: " + statistics.getAverageSuccessfulGenerationsStdDev());
+ }
+
+ /**
+ * Used internally for reporting improvement, which happens independently of
+ * the report interval parameter.
+ */
+ private void printImprovement() {
+ resources.println("[CGP] Generation: " + resources.currentGeneration() + ", fittest chromosome ("
+ + population.getFittestIndex() + ") has fitness: " + population.getFittest().getFitness());
+ }
+
+ /**
+ * Used internally for reporting generation information, which is affected
+ * by the report interval parameter.
+ */
private void reportGeneration() {
resources.reportln("[CGP] Generation: " + resources.currentGeneration() + ", fittest chromosome ("
+ population.getFittestIndex() + ") has fitness: " + population.getFittest().getFitness());
}
+ /**
+ * This method calls {@code nextGeneration()} in a loop
+ * until the experiment is flagged as finished. This is
+ * performed on the same thread of execution, so this
+ * method will most likely block for a significant amount
+ * of time (problem-dependent, but anywhere from seconds to days).
+ * <br>
+ * Once the experiment is finished, calling this method does
+ * nothing until {@code reset()} is called.
+ */
public void start() {
if (!finished) {
while (!finished) {
@@ -243,9 +468,19 @@ public class JCGP {
}
}
+ /**
+ * Resets the experiment.
+ * <br>
+ * More specifically: this creates a new population, resets
+ * the current generation and run parameters to 1 and prints
+ * a complete list of the experiment's parameters.
+ *
+ */
public void reset() {
finished = false;
+ statistics = new StatisticsLogger();
population = new Population(resources);
+ resetStatisticsValues();
resources.setCurrentGeneration(1);
resources.setCurrentRun(1);
resources.println("*********************************************************");
@@ -262,30 +497,82 @@ public class JCGP {
resources.println("[CGP] Evolutionary strategy: " + evolutionaryStrategy.toString());
resources.println("[CGP] Mutator: " + mutator.toString());
}
+
+ /**
+ * Internally used to reset the fields used
+ * for logging results statistics.
+ */
+ private void resetStatisticsValues() {
+ problem.reset();
+ lastImprovementGeneration = 0;
+ bestFitnessFound = 0;
+ activeNodes = 0;
+ }
+ /**
+ * When given a .par file, this method loads the parameters into the
+ * experiment's resources. This causes an experiment-wide reset.
+ *
+ * @param file the file to parse.
+ */
public void loadParameters(File file) {
ParameterParser.parse(file, resources);
FunctionParser.parse(file, problem.getFunctionSet(), resources);
reset();
}
+ /**
+ * Parses a problem data file. This is problem-dependent, not
+ * all problems require a data file.
+ *
+ * @param file the file to parse.
+ */
public void loadProblemData(File file) {
problem.parseProblemData(file, resources);
reset();
}
+ /**
+ * Loads a chromosome from the given file into
+ * the specified population index.
+ *
+ * @param file the chromosome to parse.
+ * @param chromosomeIndex the population index into which to parse.
+ */
public void loadChromosome(File file, int chromosomeIndex) {
ChromosomeParser.parse(file, population.getChromosome(chromosomeIndex), resources);
}
+ /**
+ * Saves a copy of the specified chromosome
+ * into the given file.
+ *
+ * @param file the target file.
+ * @param chromosomeIndex the index of the chromosome to save.
+ */
public void saveChromosome(File file, int chromosomeIndex) {
ChromosomeParser.save(file, population.getChromosome(chromosomeIndex), resources);
}
+ /**
+ * Returns the experiment's status. When finished, the only
+ * way to continue is by calling {@code reset()}.
+ *
+ * @return true if the experiment is finished.
+ */
public boolean isFinished() {
return finished;
}
+ /**
+ * Sets an extra console. The entire JCGP library prints
+ * messages to {@code System.console()} but also to an
+ * additional console, if one is defined. This is used so
+ * that messages are printed on a user interface as well,
+ * or written directly to a file, for example.
+ *
+ * @param console the extra console to be used.
+ */
public void setConsole(Console console) {
resources.setConsole(console);
}
diff --git a/src/jcgp/backend/function/DigitalCircuitFunctions.java b/src/jcgp/backend/function/DigitalCircuitFunctions.java
index 31cdf17..0d4ae8e 100644
--- a/src/jcgp/backend/function/DigitalCircuitFunctions.java
+++ b/src/jcgp/backend/function/DigitalCircuitFunctions.java
@@ -1,10 +1,23 @@
package jcgp.backend.function;
+/**
+ * This class contains all digital circuit functions
+ * (defined as unsigned integer functions in the classic
+ * CGP implementation) defined in static nested classes.
+ * <br>
+ * This is the function set used by DigitalCircuitProblem.
+ *
+ * @see DigitalCircuiProblem
+ * @author Eduardo Pedroni
+ *
+ */
public class DigitalCircuitFunctions extends FunctionSet {
+ /**
+ * Creates a new instance of DigitalCircuitFunctions.
+ */
public DigitalCircuitFunctions() {
- name = "32-bit Logic";
- functionList = new Function[]{
+ registerFunctions(
new ConstantZero(),
new ConstantOne(),
new WireA(),
@@ -25,11 +38,12 @@ public class DigitalCircuitFunctions extends FunctionSet {
new Mux2(),
new Mux3(),
new Mux4()
- };
-
- enableAll();
+ );
}
+ /**
+ * Outputs a constant 0, has no inputs.
+ */
public static class ConstantZero extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -47,6 +61,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Outputs a constant 1, has no inputs.
+ */
public static class ConstantOne extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -64,6 +81,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Connects one node to another with no function.
+ */
public static class WireA extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -85,6 +105,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Connects one node to another with no function.
+ */
public static class WireB extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -106,6 +129,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Inverts input, equivalent to inverter logic gate.
+ */
public static class NotA extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -127,6 +153,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Inverts input, equivalent to inverter logic gate.
+ */
public static class NotB extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -148,6 +177,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * ANDs inputs together.
+ */
public static class And extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -173,6 +205,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * ANDs inputs together with one input inverted.
+ */
public static class AndNotA extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -198,6 +233,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * ANDs inputs together with one input inverted.
+ */
public static class AndNotB extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -223,6 +261,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * NORs inputs together.
+ */
public static class Nor extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -248,6 +289,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * XORs inputs together.
+ */
public static class Xor extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -273,6 +317,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * XNORs inputs together.
+ */
public static class Xnor extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -298,6 +345,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * ORs inputs together.
+ */
public static class Or extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -323,6 +373,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * ORs inputs together with one inverted input.
+ */
public static class OrNotA extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -348,6 +401,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * ORs inputs together with one inverted input.
+ */
public static class OrNotB extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -373,6 +429,9 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * NANDs inputs together.
+ */
public static class Nand extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -398,6 +457,10 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Works as a multiplexer. Outputs either one of its two inputs
+ * depending on a third input (select).
+ */
public static class Mux1 extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -424,6 +487,10 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Works as a multiplexer. Outputs either one of its two inputs
+ * depending on a third input (select). Input 0 is inverted.
+ */
public static class Mux2 extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -450,6 +517,10 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Works as a multiplexer. Outputs either one of its two inputs
+ * depending on a third input (select). Input 1 is inverted.
+ */
public static class Mux3 extends Function {
@Override
public UnsignedInteger run(Object... args) {
@@ -476,6 +547,10 @@ public class DigitalCircuitFunctions extends FunctionSet {
}
}
+ /**
+ * Works as a multiplexer. Outputs either one of its two inputs
+ * depending on a third input (select). Both inputs are inverted.
+ */
public static class Mux4 extends Function {
@Override
public UnsignedInteger run(Object... args) {
diff --git a/src/jcgp/backend/function/Function.java b/src/jcgp/backend/function/Function.java
index fdacac0..2e1f3c6 100644
--- a/src/jcgp/backend/function/Function.java
+++ b/src/jcgp/backend/function/Function.java
@@ -7,17 +7,15 @@ package jcgp.backend.function;
* any arbitrary operation on the arguments specified. It must also override
* {@code getArity()} to return the function arity.
*
- *
* @author Eduardo Pedroni
- *
*/
public abstract class Function {
/**
* Executes the function.
*
- * @param args the function arguments
- * @return the function result
+ * @param args the function arguments.
+ * @return the function result.
*/
public abstract Object run(Object... args);
diff --git a/src/jcgp/backend/function/FunctionSet.java b/src/jcgp/backend/function/FunctionSet.java
index 926ed68..052183a 100644
--- a/src/jcgp/backend/function/FunctionSet.java
+++ b/src/jcgp/backend/function/FunctionSet.java
@@ -4,9 +4,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
-
/**
- * FunctionSet encapsulates a group of functions. This is usually done to
+ * FunctionSet encapsulates a group of functions. This is done to
* simplify the implementation of problem types.
* <br><br>
* FunctionSet contains a variety of useful methods for acquiring general
@@ -18,18 +17,16 @@ import java.util.Iterator;
* allowed functions only, providing an easy way to control which functions
* can be used in mutations.
* <br><br>
- * An implementation of FunctionSet must simply set the name field and initialise
- * the functionList array with all of the functions. It is advisable to call
- * {@code enableAll()} to enable all functions once the array is initialised.
- *
+ * An implementation of FunctionSet must simply use its constructor to set
+ * the name field and use {@code registerFunctions()} to add the required
+ * functions.
*
* @author Eduardo Pedroni
*
*/
public abstract class FunctionSet {
- protected Function[] functionList;
- protected ArrayList<Integer> allowedFunctions;
- protected String name;
+ private ArrayList<Function> functionList = new ArrayList<Function>();
+ private ArrayList<Integer> allowedFunctions = new ArrayList<Integer>();
/**
* @return the number of currently allowed functions.
@@ -42,7 +39,7 @@ public abstract class FunctionSet {
* @return the total number of functions, including disabled ones.
*/
public int getTotalFunctionCount() {
- return functionList.length;
+ return functionList.size();
}
/**
@@ -54,7 +51,7 @@ public abstract class FunctionSet {
* @return the allowed function object.
*/
public Function getAllowedFunction(int index) {
- return functionList[allowedFunctions.get(index)];
+ return functionList.get(allowedFunctions.get(index));
}
/**
@@ -67,7 +64,7 @@ public abstract class FunctionSet {
* @return the function object.
*/
public Function getFunction(int index) {
- return functionList[index];
+ return functionList.get(index);
}
/**
@@ -100,7 +97,7 @@ public abstract class FunctionSet {
* list of allowed functions and removes any elements which are equal
* to the specified index.
*/
- if (index < functionList.length) {
+ if (index < functionList.size()) {
for (Iterator<Integer> iterator = allowedFunctions.iterator(); iterator.hasNext();) {
int function = iterator.next();
if (function == index) {
@@ -108,7 +105,7 @@ public abstract class FunctionSet {
}
}
} else {
- throw new ArrayIndexOutOfBoundsException("Function " + index + " does not exist, the set only has " + functionList.length + " functions.");
+ throw new ArrayIndexOutOfBoundsException("Function " + index + " does not exist, the set only has " + functionList.size() + " functions.");
}
}
@@ -126,11 +123,6 @@ public abstract class FunctionSet {
}
}
- @Override
- public String toString() {
- return name;
- }
-
/**
* Checks if a specified function is enabled. If the function
* does not belong in the FunctionSet, this returns false.
@@ -140,7 +132,7 @@ public abstract class FunctionSet {
*/
public boolean isEnabled(Function function) {
for (int i = 0; i < allowedFunctions.size(); i++) {
- if (functionList[allowedFunctions.get(i)] == function) {
+ if (functionList.get(allowedFunctions.get(i)) == function) {
return true;
}
}
@@ -148,12 +140,34 @@ public abstract class FunctionSet {
}
/**
- * Enables all functions.
+ * For internal use in subclass constructors. This method
+ * adds the specified functions and enables them. The same
+ * function cannot be added more than once.
+ *
+ * @param functions the functions to register in the function set.
*/
- public void enableAll() {
- allowedFunctions = new ArrayList<Integer>();
- for (int i = 0; i < functionList.length; i++) {
- allowedFunctions.add(i);
+ protected void registerFunctions(Function... functions) {
+ for (int i = 0; i < functions.length; i++) {
+ if (!alreadyHave(functions[i])) {
+ functionList.add(functions[i]);
+ enableFunction(functionList.size() - 1);
+ }
}
}
+
+ /**
+ * For internal use only, this checks whether a function
+ * is already present in the function set.
+ *
+ * @param function the function to look for.
+ * @return true if the function is already in the function set.
+ */
+ private boolean alreadyHave(Function function) {
+ for (int i = 0; i < functionList.size(); i++) {
+ if (functionList.get(i).getClass() == function.getClass()) {
+ return true;
+ }
+ }
+ return false;
+ }
} \ No newline at end of file
diff --git a/src/jcgp/backend/function/SymbolicRegressionFunctions.java b/src/jcgp/backend/function/SymbolicRegressionFunctions.java
index a35f258..6f72723 100644
--- a/src/jcgp/backend/function/SymbolicRegressionFunctions.java
+++ b/src/jcgp/backend/function/SymbolicRegressionFunctions.java
@@ -1,12 +1,25 @@
package jcgp.backend.function;
+/**
+ * This class contains all symbolic regression functions
+ * (defined as double functions in the classic CGP implementation)
+ * in static nested classes.
+ * <br>
+ * This is the function set used by SymbolicRegressionProblem.
+ *
+ * @see SymbolicRegressionProblem
+ * @author Eduardo Pedroni
+ *
+ */
public class SymbolicRegressionFunctions extends FunctionSet {
public final static double DIVISION_LIMIT = 0.0001;
+ /**
+ * Creates a new instance of SymbolicRegressionFunctions.
+ */
public SymbolicRegressionFunctions() {
- name = "Symbolic regression functions";
- functionList = new Function[] {
+ registerFunctions(
new Absolute(),
new SquareRoot(),
new Reciprocal(),
@@ -26,9 +39,7 @@ public class SymbolicRegressionFunctions extends FunctionSet {
new Addition(),
new Subtraction(),
new Multiplication(),
- new Division()};
-
- enableAll();
+ new Division());
}
/**
diff --git a/src/jcgp/backend/function/TravellingSalesmanFunctions.java b/src/jcgp/backend/function/TravellingSalesmanFunctions.java
index 472b7ad..06b44bb 100644
--- a/src/jcgp/backend/function/TravellingSalesmanFunctions.java
+++ b/src/jcgp/backend/function/TravellingSalesmanFunctions.java
@@ -1,10 +1,21 @@
package jcgp.backend.function;
+/**
+ * This class contains all travelling salesman functions in static nested classes.
+ * <br>
+ * This is the function set used by TravellingSalesmanProblem.
+ *
+ * @see TravellingSalesmanProblem
+ * @author Eduardo Pedroni
+ *
+ */
public class TravellingSalesmanFunctions extends FunctionSet {
+ /**
+ * Creates a new instance of TravellingSalesmanFunctions.
+ */
public TravellingSalesmanFunctions() {
- name = "Travelling salesman functions";
- functionList = new Function[]{
+ registerFunctions(
new SquareRoot(),
new Square(),
new Cube(),
@@ -16,9 +27,7 @@ public class TravellingSalesmanFunctions extends FunctionSet {
new ScaledAddition(),
new SymmetricSubtraction(),
new Multiplication(),
- new BoundedDivision() };
-
- enableAll();
+ new BoundedDivision());
}
/**
diff --git a/src/jcgp/backend/function/UnsignedInteger.java b/src/jcgp/backend/function/UnsignedInteger.java
index 7feb33f..d23862c 100644
--- a/src/jcgp/backend/function/UnsignedInteger.java
+++ b/src/jcgp/backend/function/UnsignedInteger.java
@@ -14,7 +14,12 @@ package jcgp.backend.function;
* signed and will behave as such for all arithmetic operations.
* Bitwise operations can still be performed as they work at the bit
* level, making this data type particularly suitable for circuit design.
- *
+ * <br><br>
+ * TODO in the unlikely event that unsigned integers are natively
+ * implemented in Java, they should be used instead of this class.
+ * <br><br>
+ * Why are unsigned integers not supported?<br>
+ * http://stackoverflow.com/questions/430346/why-doesnt-java-support-unsigned-ints
*
* @author Eduardo Pedroni
* @see Integer
@@ -27,7 +32,7 @@ public class UnsignedInteger {
/**
* Makes a new instance of UnsignedInteger with a specified value.
*
- * @param i the value with which to initialise
+ * @param i the value with which to initialise.
*/
public UnsignedInteger(int i) {
value = new Integer(i);
@@ -36,7 +41,7 @@ public class UnsignedInteger {
/**
* Makes a new instance of UnsignedInteger with a specified value.
*
- * @param i the value with which to initialise
+ * @param i the value with which to initialise.
*/
public UnsignedInteger(Integer i) {
value = i;
@@ -46,14 +51,14 @@ public class UnsignedInteger {
* Makes a new instance of UnsignedInteger from the string representation
* of an unsigned integer.
*
- * @param i the string with which to initialise
+ * @param i the string with which to initialise.
*/
public UnsignedInteger(String i) {
value = Integer.parseUnsignedInt(i);
}
/**
- * @return the wrapped Integer object
+ * @return the wrapped Integer object.
*/
public Integer get() {
return value;
@@ -61,6 +66,11 @@ public class UnsignedInteger {
@Override
public String toString() {
+ /*
+ * It is important to override this so that
+ * the visual representation of the integer
+ * is unsigned as well.
+ */
return Integer.toUnsignedString(value);
}
}
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;
}
}
diff --git a/src/jcgp/backend/parameters/BooleanParameter.java b/src/jcgp/backend/parameters/BooleanParameter.java
new file mode 100644
index 0000000..f0abe50
--- /dev/null
+++ b/src/jcgp/backend/parameters/BooleanParameter.java
@@ -0,0 +1,79 @@
+package jcgp.backend.parameters;
+
+import javafx.beans.property.SimpleBooleanProperty;
+
+/**
+ * Parameter subclass for the boolean type. Most of the
+ * functionality is already implemented in {@code Parameter},
+ * leaving only construction and type definition to the
+ * subclasses.
+ * <br><br>
+ * This class contains three constructors, two of which are public.
+ * One assumes the parameter is not critical and only takes a name
+ * and initial value, while the other allows the critical flag
+ * to be set as well. The third constructor is protected and allows
+ * the monitor flag to be set as well, allowing subclasses of this class
+ * to be used as monitors. See {@link BooleanMonitor} for an example
+ * of this usage.
+ * <br><br>
+ * The validate method is overridden here and left blank since not all
+ * parameters actually require validation, but where validation is
+ * required this method can be anonymously overridden on an instance-to-instance
+ * basis.
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class BooleanParameter extends Parameter<Boolean> {
+
+ /**
+ * Creates a new instance of this class, assuming the parameter
+ * is not critical.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ */
+ public BooleanParameter(boolean value, String name) {
+ super(name, false, false);
+ this.valueProperty = new SimpleBooleanProperty(value);
+ }
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ * @param critical true if the parameter is critical.
+ */
+ public BooleanParameter(boolean value, String name, boolean critical) {
+ super(name, false, critical);
+ this.valueProperty = new SimpleBooleanProperty(value);
+ }
+
+ /**
+ * For use by subclasses only, this constructor allows the monitor flag to be set.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ * @param monitor true if the parameter is a monitor.
+ * @param critical true if the parameter is critical.
+ */
+ protected BooleanParameter(boolean value, String name, boolean monitor, boolean critical) {
+ super(name, monitor, critical);
+ this.valueProperty = new SimpleBooleanProperty(value);
+ }
+
+ @Override
+ public Boolean get() {
+ return super.get().booleanValue();
+ }
+
+ @Override
+ public void validate(Boolean newValue) {
+ /*
+ * Blank by default.
+ * Instances should override this as necessary.
+ *
+ */
+ }
+}
diff --git a/src/jcgp/backend/parameters/DoubleParameter.java b/src/jcgp/backend/parameters/DoubleParameter.java
new file mode 100644
index 0000000..14b3151
--- /dev/null
+++ b/src/jcgp/backend/parameters/DoubleParameter.java
@@ -0,0 +1,79 @@
+package jcgp.backend.parameters;
+
+import javafx.beans.property.SimpleDoubleProperty;
+
+/**
+ * Parameter subclass for the double type. Most of the
+ * functionality is already implemented in {@code Parameter},
+ * leaving only construction and type definition to the
+ * subclasses.
+ * <br><br>
+ * This class contains three constructors, two of which are public.
+ * One assumes the parameter is not critical and only takes a name
+ * and initial value, while the other allows the critical flag
+ * to be set as well. The third constructor is protected and allows
+ * the monitor flag to be set as well, allowing subclasses of this class
+ * to be used as monitors. See {@link DoubleMonitor} for an example
+ * of this usage.
+ * <br><br>
+ * The validate method is overridden here and left blank since not all
+ * parameters actually require validation, but where validation is
+ * required this method can be anonymously overridden on an instance-to-instance
+ * basis.
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class DoubleParameter extends Parameter<Number> {
+
+ /**
+ * Creates a new instance of this class, assuming the parameter
+ * is not critical.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ */
+ public DoubleParameter(double value, String name) {
+ super(name, false, false);
+ this.valueProperty = new SimpleDoubleProperty(value);
+ }
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ * @param critical true if the parameter is critical.
+ */
+ public DoubleParameter(double value, String name, boolean critical) {
+ super(name, false, critical);
+ this.valueProperty = new SimpleDoubleProperty(value);
+ }
+
+ /**
+ * For use by subclasses only, this constructor allows the monitor flag to be set.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ * @param monitor true if the parameter is a monitor.
+ * @param critical true if the parameter is critical.
+ */
+ protected DoubleParameter(double value, String name, boolean monitor, boolean critical) {
+ super(name, monitor, critical);
+ this.valueProperty = new SimpleDoubleProperty(value);
+ }
+
+ @Override
+ public Double get() {
+ return super.get().doubleValue();
+ }
+
+ @Override
+ public void validate(Number newValue) {
+ /*
+ * Blank by default.
+ * Instances should override this as necessary.
+ *
+ */
+ }
+}
diff --git a/src/jcgp/backend/parameters/IntegerParameter.java b/src/jcgp/backend/parameters/IntegerParameter.java
new file mode 100644
index 0000000..d0d7328
--- /dev/null
+++ b/src/jcgp/backend/parameters/IntegerParameter.java
@@ -0,0 +1,79 @@
+package jcgp.backend.parameters;
+
+import javafx.beans.property.SimpleIntegerProperty;
+
+/**
+ * Parameter subclass for the double type. Most of the
+ * functionality is already implemented in {@code Parameter},
+ * leaving only construction and type definition to the
+ * subclasses.
+ * <br><br>
+ * This class contains three constructors, two of which are public.
+ * One assumes the parameter is not critical and only takes a name
+ * and initial value, while the other allows the critical flag
+ * to be set as well. The third constructor is protected and allows
+ * the monitor flag to be set as well, allowing subclasses of this class
+ * to be used as monitors. See {@link IntegerMonitor} for an example
+ * of this usage.
+ * <br><br>
+ * The validate method is overridden here and left blank since not all
+ * parameters actually require validation, but where validation is
+ * required this method can be anonymously overridden on an instance-to-instance
+ * basis.
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class IntegerParameter extends Parameter<Number> {
+
+ /**
+ * Creates a new instance of this class, assuming the parameter
+ * is not critical.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ */
+ public IntegerParameter(int value, String name) {
+ super(name, false, false);
+ this.valueProperty = new SimpleIntegerProperty(value);
+ }
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ * @param critical true if the parameter is critical.
+ */
+ public IntegerParameter(int value, String name, boolean critical) {
+ super(name, false, critical);
+ this.valueProperty = new SimpleIntegerProperty(value);
+ }
+
+ /**
+ * For use by subclasses only, this constructor allows the monitor flag to be set.
+ *
+ * @param value the initial value for this parameter.
+ * @param name the name of this parameter, for GUI display.
+ * @param monitor true if the parameter is a monitor.
+ * @param critical true if the parameter is critical.
+ */
+ protected IntegerParameter(int value, String name, boolean monitor, boolean critical) {
+ super(name, monitor, critical);
+ this.valueProperty = new SimpleIntegerProperty(value);
+ }
+
+ @Override
+ public Integer get() {
+ return super.get().intValue();
+ }
+
+ @Override
+ public void validate(Number newValue) {
+ /*
+ * Blank by default.
+ * Instances should override this as necessary.
+ *
+ */
+ }
+}
diff --git a/src/jcgp/backend/parameters/Parameter.java b/src/jcgp/backend/parameters/Parameter.java
new file mode 100644
index 0000000..c04dee9
--- /dev/null
+++ b/src/jcgp/backend/parameters/Parameter.java
@@ -0,0 +1,128 @@
+package jcgp.backend.parameters;
+
+import javafx.beans.property.Property;
+import javafx.beans.property.ReadOnlyProperty;
+
+/**
+ * Specifies an abstract model of a module parameter.
+ * <br><br>
+ * Parameters are values which control the operation of modules.
+ * They can be freely modified and accessed by the module in which
+ * they are declared. Additionally, the module may choose to expose
+ * some of its parameters to a user interface, so that information
+ * is displayed. If that is the case, the parameter can be made
+ * read-only by setting the monitor flag (it becomes a parameter
+ * monitor). In addition, settings the critical flag indicates to
+ * the experiment that any changes to the parameter should result in
+ * an experiment-wide reset.
+ * <br><br>
+ * {@code Parameter} is abstract. A typical implementation defines
+ * the data type T and initialises the {@code valueProperty} field
+ * with a suitable type. For the sake of clarity, it may not be ideal
+ * for a subclass constructor to expose an argument for the monitor
+ * field. Instead, a different class should be created which constructs
+ * the parameter as a monitor, so that the distinction between a regular
+ * parameter and a parameter monitor is more apparent. The boolean, integer
+ * and double implementations of parameter (and their associated monitors)
+ * implement this pattern, refer to them for more details.
+ * <br><br>
+ * The {@code status} field holds the current status of the parameter,
+ * which should change whenever the parameter value changes.
+ * In order for this to happen, {@code validate()} is called whenever
+ * the parameter status should be updated. This being the case, it should
+ * be overridden on an instance-to-instance basis, as each parameter
+ * will likely have different validity criteria. The type of status is
+ * {@link ParameterStatus}, an enum type defining all valid states.
+ *
+ * @see Module, ParameterStatus
+ * @author Eduardo Pedroni
+ * @param <T> the data type stored in the parameter.
+ */
+public abstract class Parameter<T> {
+
+ private boolean monitor, critical;
+ protected ParameterStatus status = ParameterStatus.VALID;
+ protected String name;
+ protected Property<T> valueProperty;
+
+ /**
+ * For internal use only. This creates a new instance of the class
+ * requiring a name, monitor and critical information. It should be
+ * invoked using {@code super()} by any implementing constructors.
+ *
+ * @param name the name of the parameter, to be used by GUIs (if in use).
+ * @param monitor true if the parameter is a monitor, meaning it is not editable via the GUI (if in use).
+ * @param critical true if any changes to this parameter should cause an experiment-wide reset.
+ */
+ protected Parameter(String name, boolean monitor, boolean critical) {
+ this.name = name;
+ this.monitor = monitor;
+ this.critical = critical;
+ }
+
+ /**
+ * @return true if the parameter is a monitor.
+ */
+ public boolean isMonitor() {
+ return monitor;
+ }
+
+ /**
+ * @return true if the parameter is critical.
+ */
+ public boolean isCritical() {
+ return critical;
+ }
+
+ /**
+ * @return the current status of the parameter.
+ */
+ public ParameterStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * This method is intended for bindings only. Changes to the parameter
+ * value should be made using {@code set()}.
+ *
+ * @return the property which holds the parameter value.
+ */
+ public ReadOnlyProperty<T> valueProperty() {
+ return valueProperty;
+ }
+
+ /**
+ * @return the parameter's current value.
+ */
+ public T get() {
+ return valueProperty.getValue();
+ }
+
+ /**
+ * Sets the parameter to the specified value, if the property
+ * is not bound.
+ *
+ * @param newValue the new value for the parameter.
+ */
+ public void set(T newValue) {
+ if (!valueProperty.isBound()) {
+ valueProperty.setValue(newValue);
+ }
+ }
+
+ /**
+ * This is a callback method which gets called whenever changes
+ * to parameters (not only its own instance) are made. This method
+ * is intended to set the {@code status} field according to the
+ * new value, so that the user can be informed if any parameters
+ * are currently set to invalid values.
+ *
+ * @param newValue the new value.
+ */
+ public abstract void validate(T newValue);
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/src/jcgp/backend/parameters/ParameterStatus.java b/src/jcgp/backend/parameters/ParameterStatus.java
new file mode 100644
index 0000000..4041cad
--- /dev/null
+++ b/src/jcgp/backend/parameters/ParameterStatus.java
@@ -0,0 +1,53 @@
+package jcgp.backend.parameters;
+
+/**
+ * Enum type containing all possible states for parameters.
+ * <br>
+ * <ul>
+ * <li>INVALID: the new parameter value is not valid,
+ * and the experiment will not be allowed to run.</li>
+ * <li>WARNING: the new parameter value is technically valid,
+ * though it might lead to undesirable behaviour.</li>
+ * <li>WARNING_RESET: the new parameter value is technically valid
+ * but will require a reset.</li>
+ * <li>VALID: the new value is valid.</li>
+ * <br><br>
+ * The above definitions are final in the sense that they outline
+ * how parameters are treated by the program depending on their
+ * status (e.g. if any parameters are set to WARNING_RESET, a reset
+ * will automatically be performed when the experiment is run).
+ * <br>
+ * In addition to the status itself, this class includes a field
+ * to contain details about the current status. If a GUI is in use,
+ * the contents of the field should be displayed to the user, as well
+ * as some visual indication of the status itself. Both the status
+ * and the message should be updated by each parameter when {@code validate()}
+ * is called.
+ *
+ * @see Parameter
+ * @author Eduardo Pedroni
+ *
+ */
+public enum ParameterStatus {
+ INVALID, WARNING, WARNING_RESET, VALID;
+
+ private String details;
+
+ /**
+ * Sets a new string containing details about the current status.
+ * This should be displayed by the GUI, if one is in use.
+ *
+ * @param details an explanation of the current status.
+ */
+ public void setDetails(String details) {
+ this.details = details;
+ }
+
+ /**
+ * @return the string containing details about the current status.
+ */
+ public String getDetails() {
+ return details;
+ }
+
+}
diff --git a/src/jcgp/backend/parameters/monitors/BooleanMonitor.java b/src/jcgp/backend/parameters/monitors/BooleanMonitor.java
new file mode 100644
index 0000000..c7ccaf0
--- /dev/null
+++ b/src/jcgp/backend/parameters/monitors/BooleanMonitor.java
@@ -0,0 +1,44 @@
+package jcgp.backend.parameters.monitors;
+
+import jcgp.backend.parameters.BooleanParameter;
+
+/**
+ * This is a special type of {@code BooleanParameter} which
+ * cannot be modified in the GUI (if the GUI is in use).
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class BooleanMonitor extends BooleanParameter {
+
+ /**
+ * Creates a new instance of this class, assuming the monitor
+ * is not critical.
+ *
+ * @param value the initial value for this monitor.
+ * @param name the name of this monitor, for GUI display.
+ */
+ public BooleanMonitor(boolean value, String name) {
+ super(value, name, true, false);
+ }
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param value the initial value for this monitor.
+ * @param name the name of this monitor, for GUI display.
+ * @param critical true if the monitor is critical.
+ */
+ public BooleanMonitor(boolean value, String name, boolean critical) {
+ super(value, name, true, critical);
+ }
+
+ @Override
+ public void validate(Boolean newValue) {
+ /*
+ * Blank by default.
+ * Instances should override this as necessary.
+ *
+ */
+ }
+}
diff --git a/src/jcgp/backend/parameters/monitors/DoubleMonitor.java b/src/jcgp/backend/parameters/monitors/DoubleMonitor.java
new file mode 100644
index 0000000..36b0e22
--- /dev/null
+++ b/src/jcgp/backend/parameters/monitors/DoubleMonitor.java
@@ -0,0 +1,45 @@
+package jcgp.backend.parameters.monitors;
+
+import jcgp.backend.parameters.DoubleParameter;
+
+/**
+ * This is a special type of {@code DoubleParameter} which
+ * cannot be modified in the GUI (if the GUI is in use).
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class DoubleMonitor extends DoubleParameter {
+
+ /**
+ * Creates a new instance of this class, assuming the monitor
+ * is not critical.
+ *
+ * @param value the initial value for this monitor.
+ * @param name the name of this monitor, for GUI display.
+ */
+ public DoubleMonitor(double value, String name) {
+ super(value, name, true, false);
+ }
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param value the initial value for this monitor.
+ * @param name the name of this monitor, for GUI display.
+ * @param critical true if the monitor is critical.
+ */
+ public DoubleMonitor(double value, String name, boolean critical) {
+ super(value, name, true, critical);
+ }
+
+ @Override
+ public void validate(Number newValue) {
+ /*
+ * Blank by default.
+ * Instances should override this as necessary.
+ *
+ */
+ }
+
+}
diff --git a/src/jcgp/backend/parameters/monitors/IntegerMonitor.java b/src/jcgp/backend/parameters/monitors/IntegerMonitor.java
new file mode 100644
index 0000000..5c1a83e
--- /dev/null
+++ b/src/jcgp/backend/parameters/monitors/IntegerMonitor.java
@@ -0,0 +1,44 @@
+package jcgp.backend.parameters.monitors;
+
+import jcgp.backend.parameters.IntegerParameter;
+
+/**
+ * This is a special type of {@code IntegerParameter} which
+ * cannot be modified in the GUI (if the GUI is in use).
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class IntegerMonitor extends IntegerParameter {
+
+ /**
+ * Creates a new instance of this class, assuming the monitor
+ * is not critical.
+ *
+ * @param value the initial value for this monitor.
+ * @param name the name of this monitor, for GUI display.
+ */
+ public IntegerMonitor(int value, String name) {
+ super(value, name, true, false);
+ }
+
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param value the initial value for this monitor.
+ * @param name the name of this monitor, for GUI display.
+ * @param critical true if the monitor is critical.
+ */
+ public IntegerMonitor(int value, String name, boolean critical) {
+ super(value, name, true, critical);
+ }
+
+ @Override
+ public void validate(Number newValue) {
+ /*
+ * Blank by default.
+ * Instances should override this as necessary.
+ *
+ */
+ }
+}
diff --git a/src/jcgp/backend/resources/Console.java b/src/jcgp/backend/resources/Console.java
index 6bbd5ba..2900afe 100644
--- a/src/jcgp/backend/resources/Console.java
+++ b/src/jcgp/backend/resources/Console.java
@@ -1,11 +1,42 @@
package jcgp.backend.resources;
+/**
+ * Defines the basic model for a console.
+ * <br><br>
+ * This interface will typically be implemented by a GUI class
+ * and GUI packages such as JavaFX are usually single-threaded.
+ * If the CGP experiment is running on a side thread (which would
+ * be the case so as not to block the entire GUI), updating a GUI
+ * element such as the console from a different thread would lead
+ * to concurrency problems. For this reason, this console is
+ * intended to buffer printed messages and only output them to the
+ * actual GUI control when {@code flush()} is called (which is
+ * guaranteed to be done in a thread-safe way by the library).
+ *
+ * @author Eduardo Pedroni
+ *
+ */
public interface Console {
+ /**
+ * Prints a string and automatically adds a line break at the end.
+ *
+ * @param s the string to print.
+ */
public void println(String s);
+ /**
+ * Prints a string without line break at the end (unless the string
+ * itself specifies one).
+ *
+ * @param s the string to print.
+ */
public void print(String s);
+ /**
+ * Outputs all buffered messages to the console. Only necessary
+ * if concurrent accesses must be avoided.
+ */
public void flush();
}
diff --git a/src/jcgp/backend/resources/ModifiableResources.java b/src/jcgp/backend/resources/ModifiableResources.java
index 53dc815..3dab2aa 100644
--- a/src/jcgp/backend/resources/ModifiableResources.java
+++ b/src/jcgp/backend/resources/ModifiableResources.java
@@ -3,8 +3,9 @@ package jcgp.backend.resources;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import jcgp.backend.function.FunctionSet;
-import jcgp.backend.resources.parameters.IntegerParameter;
-import jcgp.backend.resources.parameters.ParameterStatus;
+import jcgp.backend.parameters.IntegerParameter;
+import jcgp.backend.parameters.ParameterStatus;
+import jcgp.backend.parameters.monitors.IntegerMonitor;
/**
*
@@ -18,205 +19,236 @@ import jcgp.backend.resources.parameters.ParameterStatus;
*/
public class ModifiableResources extends Resources {
+ /**
+ * Creates an instance of this class and initialises
+ * all base parameters to default values. See
+ * {@code createBaseParameters} for the exact parameter
+ * initialisation.
+ */
public ModifiableResources() {
createBaseParameters();
}
- public void setValues(String filePath) {
-
- }
-
/**
- * @param rows the rows to set
+ * @param rows the number of rows to set.
*/
public void setRows(int rows) {
this.rows.set(rows);
}
/**
- * @param columns the columns to set
+ * @param columns the number of columns to set.
*/
public void setColumns(int columns) {
this.columns.set(columns);
}
/**
- * @param inputs the inputs to set
+ * @param inputs the number of inputs to set.
*/
public void setInputs(int inputs) {
this.inputs.set(inputs);
}
/**
- * @param outputs the outputs to set
+ * @param outputs the number of outputs to set.
*/
public void setOutputs(int outputs) {
this.outputs.set(outputs);
}
/**
- * @param populationSize the populationSize to set
+ * @param populationSize the population size to set.
*/
public void setPopulationSize(int populationSize) {
this.populationSize.set(populationSize);
}
/**
- * @param levelsBack the levelsBack to set
+ * @param levelsBack the levels back to set.
*/
public void setLevelsBack(int levelsBack) {
this.levelsBack.set(levelsBack);
}
/**
- * @param currentGeneration the currentGeneration to set
+ * @param currentGeneration the current generation to set.
*/
public void setCurrentGeneration(int currentGeneration) {
this.currentGeneration.set(currentGeneration);
}
+
+ /**
+ * Adds 1 to the current generation.
+ */
+ public void incrementGeneration() {
+ this.currentGeneration.set(currentGeneration.get() + 1);
+ }
/**
- * @param generations the generations to set
+ * @param generations the total generations to set.
*/
public void setGenerations(int generations) {
this.generations.set(generations);
}
-
+
/**
- * @param currentRun the currentRun to set
+ * @param currentRun the current run to set.
*/
public void setCurrentRun(int currentRun) {
this.currentRun.set(currentRun);
}
/**
- * @param runs the runs to set
+ * Adds 1 to the current generation.
+ */
+ public void incrementRun() {
+ currentRun.set(currentRun.get() + 1);
+ }
+
+ /**
+ * @param runs the total runs to set.
*/
public void setRuns(int runs) {
this.runs.set(runs);
}
/**
- * @param arity the arity to set
+ * This is called automatically by the experiment when the arity changes.
+ *
+ * @param arity the arity to set.
*/
public void setArity(int arity) {
this.arity.set(arity);
}
/**
- * @param seed the seed to set
+ * @param seed the seed to set.
*/
public void setSeed(int seed) {
this.seed.set(seed);
}
/**
- * @param report the report to set
+ * @param report the report interval to set.
*/
public void setReportInterval(int report) {
this.reportInterval.set(report);
}
/**
- * @return the rows
+ * @return the rows parameter.
*/
public IntegerParameter getRowsParameter() {
return rows;
}
/**
- * @return the columns
+ * @return the columns parameter.
*/
public IntegerParameter getColumnsParameter() {
return columns;
}
/**
- * @return the inputs
+ * @return the inputs parameter.
*/
public IntegerParameter getInputsParameter() {
return inputs;
}
/**
- * @return the outputs
+ * @return the outputs parameter.
*/
public IntegerParameter getOutputsParameter() {
return outputs;
}
/**
- * @return the populationSize
+ * @return the population size parameter.
*/
public IntegerParameter getPopulationSizeParameter() {
return populationSize;
}
/**
- * @return the levelsBack
+ * @return the levels back parameter.
*/
public IntegerParameter getLevelsBackParameter() {
return levelsBack;
}
/**
- * @return the currentGeneration
+ * @return the current generation parameter.
*/
public IntegerParameter getCurrentGenerationParameter() {
return currentGeneration;
}
/**
- * @return the generations
+ * @return the total generations parameter.
*/
public IntegerParameter getGenerationsParameter() {
return generations;
}
/**
- * @return the currentRun
+ * @return the current run parameter.
*/
public IntegerParameter getCurrentRunParameter() {
return currentRun;
}
/**
- * @return the runs
+ * @return the total runs parameter.
*/
public IntegerParameter getRunsParameter() {
return runs;
}
/**
- * @return the arity
+ * @return the arity parameter.
*/
public IntegerParameter getArityParameter() {
return arity;
}
/**
- * @return the seed
+ * @return the seed parameter.
*/
public IntegerParameter getSeedParameter() {
return seed;
}
/**
- * @return the report
+ * @return the report interval parameter.
*/
public IntegerParameter getReportIntervalParameter() {
return reportInterval;
}
+ /**
+ * Update the current function set.
+ *
+ * @param functionSet the new function set.
+ */
public void setFunctionSet(FunctionSet functionSet) {
this.functionSet = functionSet;
setArity(functionSet.getMaxArity());
}
+ /**
+ * This can be set to null if no extra console is desired.
+ *
+ * @param console the extra console for the experiment to use.
+ */
public void setConsole(Console console) {
this.console = console;
}
+ /**
+ * For internal use only, this initialises all base parameters to default values.
+ */
private void createBaseParameters() {
rows = new IntegerParameter(5, "Rows", false, true) {
@Override
@@ -242,29 +274,9 @@ public class ModifiableResources extends Resources {
}
};
- inputs = new IntegerParameter(3, "Inputs", true, false) {
- @Override
- public void validate(Number newValue) {
- if (newValue.intValue() <= 0) {
- status = ParameterStatus.INVALID;
- status.setDetails("Chromosome must have at least 1 input.");
- } else {
- status = ParameterStatus.VALID;
- }
- }
- };
+ inputs = new IntegerMonitor(3, "Inputs");
- outputs = new IntegerParameter(3, "Outputs", true, false) {
- @Override
- public void validate(Number newValue) {
- if (newValue.intValue() <= 0) {
- status = ParameterStatus.INVALID;
- status.setDetails("Chromosome must have at least 1 output.");
- } else {
- status = ParameterStatus.VALID;
- }
- }
- };
+ outputs = new IntegerMonitor(3, "Outputs");
populationSize = new IntegerParameter(5, "Population", false, true) {
@Override
@@ -308,12 +320,7 @@ public class ModifiableResources extends Resources {
}
};
- currentGeneration = new IntegerParameter(1, "Generation", true, false) {
- @Override
- public void validate(Number newValue) {
- // blank
- }
- };
+ currentGeneration = new IntegerMonitor(1, "Generation");
runs = new IntegerParameter(5, "Runs") {
@Override
@@ -330,19 +337,9 @@ public class ModifiableResources extends Resources {
}
};
- currentRun = new IntegerParameter(1, "Run", true, false) {
- @Override
- public void validate(Number newValue) {
- // blank
- }
- };
+ currentRun = new IntegerMonitor(1, "Run");
- arity = new IntegerParameter(0, "Max arity", true, false) {
- @Override
- public void validate(Number newValue) {
- // blank
- }
- };
+ arity = new IntegerMonitor(0, "Max arity");
seed = new IntegerParameter(1234, "Seed", false, true) {
@Override
diff --git a/src/jcgp/backend/resources/Resources.java b/src/jcgp/backend/resources/Resources.java
index e1438f1..1f79627 100644
--- a/src/jcgp/backend/resources/Resources.java
+++ b/src/jcgp/backend/resources/Resources.java
@@ -4,13 +4,24 @@ import java.util.Random;
import jcgp.backend.function.Function;
import jcgp.backend.function.FunctionSet;
-import jcgp.backend.resources.parameters.IntegerParameter;
+import jcgp.backend.parameters.IntegerParameter;
/**
*
* The resources class encapsulates all of the resources based on which the program operates.
* Each instance of JCGP contains a single instance of Resources, which gets passed to the selected
* modules as the program executes.
+ * <br><br>
+ * The experiment's {@code Resources} object is passed to modules as the program operates, and
+ * the actual parameter values can be obtained using getter methods. Note that, for code brevity,
+ * this class's getters do not start with the word "get". For instance, to get the number of rows,
+ * one would use {@code rows()} instead of {@code getRows()}.
+ * <br><br>
+ * In addition to parameters, this class also offers utility methods. Any necessary random numbers
+ * should be obtained using {@code getRandomInt()} and {@code getRandomDouble()}. Functions from the
+ * selected function set can be obtained through this class as well. Finally, printing to the console
+ * should be done via the resources using the report and print methods, so that these prints also
+ * get sent to the GUI console (if one is present).
*
* @author Eduardo Pedroni
*
@@ -22,102 +33,102 @@ public class Resources {
protected Random numberGenerator = new Random();
protected FunctionSet functionSet;
- // GUI console
+
protected Console console;
/**
- * @return the rows
+ * @return the number of rows.
*/
public int rows() {
return rows.get();
}
/**
- * @return the columns
+ * @return the number of columns.
*/
public int columns() {
return columns.get();
}
/**
- * @return the inputs
+ * @return the number of inputs.
*/
public int inputs() {
return inputs.get();
}
/**
- * @return the outputs
+ * @return the number of outputs.
*/
public int outputs() {
return outputs.get();
}
/**
- * @return the populationSize
+ * @return the population size.
*/
public int populationSize() {
return populationSize.get();
}
/**
- * @return the levelsBack
+ * @return the levels back value.
*/
public int levelsBack() {
return levelsBack.get();
}
/**
- * @return the nodes
+ * @return the total number of nodes.
*/
public int nodes() {
return columns.get() * rows.get();
}
/**
- * @return the currentGeneration
+ * @return the current generation.
*/
public int currentGeneration() {
return currentGeneration.get();
}
/**
- * @return the generations
+ * @return the total number of generations.
*/
public int generations() {
return generations.get();
}
/**
- * @return the currentRun
+ * @return the current run.
*/
public int currentRun() {
return currentRun.get();
}
/**
- * @return the runs
+ * @return the total number of runs.
*/
public int runs() {
return runs.get();
}
/**
- * @return the arity
+ * @return the maximum arity out of the function set.
*/
public int arity() {
return arity.get();
}
/**
- * @return the seed
+ * @return the random seed being used.
*/
public int seed() {
return seed.get();
}
/**
- * @return the report interval
+ * @return the report interval.
*/
public int reportInterval() {
return reportInterval.get();
@@ -126,14 +137,37 @@ public class Resources {
/*
* Utility functions
*/
+ /**
+ * Gets the next random integer using the experiment's random
+ * number generator. The integer returned will be between 0 (inclusive)
+ * and limit (exclusive).
+ *
+ * @param limit the limit value.
+ * @return a random integer between 0 and limit.
+ */
public int getRandomInt(int limit) {
return numberGenerator.nextInt(limit);
}
+ /**
+ * Gets the next random double using the experiment's random
+ * number generator. The double returned will be between 0 (inclusive)
+ * and limit (exclusive).
+ *
+ * @param limit the limit value.
+ * @return a random double between 0 and limit.
+ */
public double getRandomDouble(int limit) {
return numberGenerator.nextDouble() * limit;
}
+ /**
+ * Gets the next random integer using the experiment's random
+ * number generator. The integer returned will be between 0 (inclusive)
+ * and 1 (exclusive).
+ *
+ * @return a random integer between 0 and 1.
+ */
public double getRandomDouble() {
return numberGenerator.nextDouble();
}
@@ -141,53 +175,94 @@ public class Resources {
/*
* FunctionSet functions
*/
+ /**
+ * Gets a random allowed function from the problem function set.
+ * This function uses {@code getRandomInt()} to choose the random
+ * function.
+ *
+ * @return a random allowed function.
+ */
public Function getRandomFunction() {
Function f = functionSet.getAllowedFunction(numberGenerator.nextInt(functionSet.getAllowedFunctionCount()));
return f;
}
+ /**
+ * Gets the indexed function out of the
+ * complete set of functions.
+ *
+ * @param index the function to return.
+ * @return the indexed function.
+ */
public Function getFunction(int index) {
- return functionSet.getAllowedFunction(index);
+ return functionSet.getFunction(index);
}
/**
- * @return the functionSet
+ * @return the problem's function set.
*/
public FunctionSet getFunctionSet() {
return functionSet;
}
+ /**
+ * Returns the index of a specified function. If the function is not found,
+ * -1 is returned.
+ *
+ * @param function the function with unknown index.
+ * @return the index of the function, or -1 if it was not found.
+ */
public int getFunctionIndex(Function function) {
for (int i = 0; i < functionSet.getTotalFunctionCount(); i++) {
if (function == functionSet.getFunction(i)) {
return i;
}
}
- // not found, default to 0
- return 0;
+ // not found, default to -1
+ return -1;
}
/*
* Console functionality
* These are affected by parameter report interval
*/
- public void reportln(String s) {
+ /**
+ * Prints a message to the consoles taking into account the
+ * report interval parameter. If no reports are allowed in
+ * the current generation, this does nothing.
+ * <br>
+ * This method automatically appends a line break to the message
+ * being printed.
+ *
+ * @param message the message to print.
+ */
+ public void reportln(String message) {
if (reportInterval.get() > 0) {
if (currentGeneration.get() % reportInterval.get() == 0) {
- System.out.println(s);
+ System.out.println(message);
if (console != null) {
- console.println(s);
+ console.println(message);
}
}
}
}
- public void report(String s) {
+ /**
+ * Prints a message to the consoles taking into account the
+ * report interval parameter. If no reports are allowed in
+ * the current generation, this does nothing.
+ * <br>
+ * This method does not append a line break to the message
+ * being printed.
+ *
+ * @param message the message to print.
+ */
+ public void report(String message) {
if (reportInterval.get() > 0) {
if (currentGeneration.get() % reportInterval.get() == 0) {
- System.out.print(s);
+ System.out.print(message);
if (console != null) {
- console.print(s);
+ console.print(message);
}
}
}
@@ -197,17 +272,39 @@ public class Resources {
* Console functionality
* These are not affected by parameter report interval
*/
- public void println(String s) {
- System.out.println(s);
+ /**
+ * Prints a message to the consoles ignoring
+ * report interval. In other words, messages printed
+ * using this method will always appear (though the
+ * GUI console will still need to be flushed).
+ * <br>
+ * This method automatically appends a line break to the message
+ * being printed.
+ *
+ * @param message the message to print.
+ */
+ public void println(String message) {
+ System.out.println(message);
if (console != null) {
- console.println(s);
+ console.println(message);
}
}
- public void print(String s) {
- System.out.print(s);
+ /**
+ * Prints a message to the consoles ignoring
+ * report interval. In other words, messages printed
+ * using this method will always appear (though the
+ * GUI console will still need to be flushed).
+ * <br>
+ * This method does not append a line break to the message
+ * being printed.
+ *
+ * @param message the message to print.
+ */
+ public void print(String message) {
+ System.out.print(message);
if (console != null) {
- console.print(s);
+ console.print(message);
}
}
} \ No newline at end of file
diff --git a/src/jcgp/backend/resources/parameters/BooleanParameter.java b/src/jcgp/backend/resources/parameters/BooleanParameter.java
deleted file mode 100644
index cc74a64..0000000
--- a/src/jcgp/backend/resources/parameters/BooleanParameter.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package jcgp.backend.resources.parameters;
-
-import javafx.beans.property.SimpleBooleanProperty;
-
-public abstract class BooleanParameter extends Parameter<Boolean> {
-
- public BooleanParameter(boolean value, String name, boolean monitor, boolean critical) {
- super(name, monitor, critical);
- this.valueProperty = new SimpleBooleanProperty(value);
- }
-
- /**
- * Simple BooleanParameter constructor,
- *
- *
- * @param value
- * @param name
- */
- public BooleanParameter(boolean value, String name) {
- super(name, false, false);
- this.valueProperty = new SimpleBooleanProperty(value);
- }
-}
diff --git a/src/jcgp/backend/resources/parameters/DoubleParameter.java b/src/jcgp/backend/resources/parameters/DoubleParameter.java
deleted file mode 100644
index b109446..0000000
--- a/src/jcgp/backend/resources/parameters/DoubleParameter.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package jcgp.backend.resources.parameters;
-
-import javafx.beans.property.SimpleDoubleProperty;
-
-public abstract class DoubleParameter extends Parameter<Number> {
-
- public DoubleParameter(double value, String name, boolean monitor, boolean critical) {
- super(name, monitor, critical);
- this.valueProperty = new SimpleDoubleProperty(value);
- }
-
- public DoubleParameter(double value, String name) {
- super(name, false, false);
- this.valueProperty = new SimpleDoubleProperty(value);
- }
-
- @Override
- public Double get() {
- return super.get().doubleValue();
- }
-}
diff --git a/src/jcgp/backend/resources/parameters/IntegerParameter.java b/src/jcgp/backend/resources/parameters/IntegerParameter.java
deleted file mode 100644
index 7cf68bd..0000000
--- a/src/jcgp/backend/resources/parameters/IntegerParameter.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package jcgp.backend.resources.parameters;
-
-import javafx.beans.property.SimpleIntegerProperty;
-
-public abstract class IntegerParameter extends Parameter<Number> {
-
- public IntegerParameter(int value, String name, boolean monitor, boolean critical) {
- super(name, monitor, critical);
- this.valueProperty = new SimpleIntegerProperty(value);
- }
-
- public IntegerParameter(int value, String name) {
- super(name, false, false);
- this.valueProperty = new SimpleIntegerProperty(value);
- }
-
- @Override
- public Integer get() {
- return super.get().intValue();
- }
-}
diff --git a/src/jcgp/backend/resources/parameters/Parameter.java b/src/jcgp/backend/resources/parameters/Parameter.java
deleted file mode 100644
index 3990ae6..0000000
--- a/src/jcgp/backend/resources/parameters/Parameter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package jcgp.backend.resources.parameters;
-
-import javafx.beans.property.Property;
-import javafx.beans.property.ReadOnlyProperty;
-
-public abstract class Parameter<T> {
-
- protected boolean monitor, critical, reset = false;
-
- protected ParameterStatus status = ParameterStatus.VALID;
-
- protected String name;
-
- protected Property<T> valueProperty;
-
- public Parameter(String name, boolean monitor, boolean critical) {
- this.name = name;
- this.monitor = monitor;
- this.critical = critical;
- }
-
- public boolean isMonitor() {
- return monitor;
- }
-
- public boolean isCritical() {
- return critical;
- }
-
- public boolean requiresReset() {
- return critical || reset;
- }
-
- public String getName() {
- return name;
- }
-
- public ParameterStatus getStatus() {
- return status;
- }
-
- public ReadOnlyProperty<T> valueProperty() {
- return valueProperty;
- }
-
- public T get() {
- return valueProperty.getValue();
- }
-
- public void set(T newValue) {
- if (!valueProperty.isBound()) {
- valueProperty.setValue(newValue);
- }
- }
-
- public abstract void validate(T newValue);
-}
diff --git a/src/jcgp/backend/resources/parameters/ParameterStatus.java b/src/jcgp/backend/resources/parameters/ParameterStatus.java
deleted file mode 100644
index 11da4c2..0000000
--- a/src/jcgp/backend/resources/parameters/ParameterStatus.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package jcgp.backend.resources.parameters;
-
-public enum ParameterStatus {
- INVALID, WARNING, WARNING_RESET, VALID;
-
- private String details;
-
- public void setDetails(String details) {
- this.details = details;
- }
-
- public String getDetails() {
- return details;
- }
-
-}
diff --git a/src/jcgp/backend/statistics/RunEntry.java b/src/jcgp/backend/statistics/RunEntry.java
new file mode 100644
index 0000000..0953c49
--- /dev/null
+++ b/src/jcgp/backend/statistics/RunEntry.java
@@ -0,0 +1,62 @@
+package jcgp.backend.statistics;
+
+/**
+ * This class encapsulates the data contained in a log entry.
+ * <br><br>
+ * Once constructed, data can only be retrieved. Note that
+ * the generation argument in the constructor (and consequently
+ * the value returned by {@code getGeneration()} refer to the
+ * last generation when improvement occurred.
+ *
+ * @see StatisticsLogger
+ * @author Eduardo Pedroni
+ *
+ */
+public class RunEntry {
+
+ private int generation, activeNodes;
+ private double bestFitness;
+ private boolean successful;
+
+ /**
+ * Creates a new run entry for a logger.
+ *
+ * @param generation the generation when fitness improvement last occurred.
+ * @param fitness the best fitness achieved.
+ * @param active the number of active nodes in the best solution found.
+ * @param successful whether or not the run found a perfect solution.
+ */
+ public RunEntry(int generation, double fitness, int active, boolean successful) {
+ this.generation = generation;
+ this.bestFitness = fitness;
+ this.activeNodes = active;
+ this.successful = successful;
+ }
+
+ /**
+ * @return the generation when improvement last occurred.
+ */
+ public int getGeneration() {
+ return generation;
+ }
+ /**
+ * @return the best fitness achieved during the run.
+ */
+ public double getFitness() {
+ return bestFitness;
+ }
+ /**
+ * @return true if the run was successful.
+ */
+ public boolean isSuccessful() {
+ return successful;
+ }
+
+ /**
+ * @return the number of active nodes in the best solution found.
+ */
+ public int getActiveNodes() {
+ return activeNodes;
+ }
+
+}
diff --git a/src/jcgp/backend/statistics/StatisticsLogger.java b/src/jcgp/backend/statistics/StatisticsLogger.java
new file mode 100644
index 0000000..dfbcdbe
--- /dev/null
+++ b/src/jcgp/backend/statistics/StatisticsLogger.java
@@ -0,0 +1,233 @@
+package jcgp.backend.statistics;
+
+import java.util.ArrayList;
+
+/**
+ * This is a utility class for logging experiment statistics when doing multiple runs.
+ * <br><br>
+ * Information about each run is added via the {@code logRun()} method. The many getters
+ * can be used to obtain statistics about the logged runs, such as success rate and average
+ * fitness.
+ * <br><br>
+ * {@code JCGP} uses this class to perform its logging and print out experiment data at the end.
+ *
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class StatisticsLogger {
+
+ // this list holds the logged entries
+ private ArrayList<RunEntry> runEntries;
+
+ /**
+ * Create a new statistics logger, use this when resetting is necessary.
+ */
+ public StatisticsLogger() {
+ runEntries = new ArrayList<RunEntry>();
+ }
+
+ /**
+ * Log a new run. Calling any of the statistics getters will
+ * now take this logged run into account as well as all previously
+ * logged runs.
+ *
+ * @param generation the last generation when improvement occurred.
+ * @param fitness the best fitness achieved in the run.
+ * @param active the number of active nodes in the best chromosome found.
+ * @param successful true if a perfect solution was found, false if otherwise.
+ */
+ public void logRun(int generation, double fitness, int active, boolean successful) {
+ runEntries.add(new RunEntry(generation, fitness, active, successful));
+ }
+
+ /**
+ * Averages the best fitness obtained in each run.
+ *
+ * @return the average fitness.
+ */
+ public double getAverageFitness() {
+ double average = 0;
+ for (RunEntry runEntry : runEntries) {
+ average += runEntry.getFitness() / runEntries.size();
+ }
+ return average;
+ }
+
+ /**
+ * Calculates the standard deviation of
+ * the best fitness obtained in each run.
+ *
+ * @return the standard deviation of average fitnesses.
+ */
+ public double getAverageFitnessStdDev() {
+ double average = getAverageFitness();
+ double temp, stdDev = 0;
+ for (RunEntry runEntry : runEntries) {
+ temp = runEntry.getFitness() - average;
+ temp = temp * temp;
+ stdDev += temp;
+ }
+ return stdDev;
+ }
+
+ /**
+ * Averages the number of active nodes in the
+ * best chromosomes obtained across all runs.
+ *
+ * @return the average number of active nodes.
+ */
+ public double getAverageActiveNodes() {
+ double average = 0;
+ for (RunEntry runEntry : runEntries) {
+ average += runEntry.getActiveNodes() / runEntries.size();
+ }
+ return average;
+ }
+
+ /**
+ * Calculates the standard deviation of
+ * the number of active nodes in the best solution
+ * in each run.
+ *
+ * @return the standard deviation of active node counts.
+ */
+ public double getAverageActiveNodesStdDev() {
+ double average = getAverageActiveNodes();
+ double temp, stdDev = 0;
+ for (RunEntry runEntry : runEntries) {
+ temp = runEntry.getActiveNodes() - average;
+ temp = temp * temp;
+ stdDev += temp;
+ }
+ return stdDev;
+ }
+
+ /**
+ * Calculates the average generation out of all runs.
+ * The generation value in each run corresponds to the
+ * last generation in which improvement happened.
+ * <br><br>
+ * Note that this method includes runs where no perfect
+ * solution was found. For the average number of generations
+ * for perfect solutions only, use {@code getAverageSuccessfulGenerations}.
+ *
+ * @return the average number of generations.
+ */
+ public double getAverageGenerations() {
+ double average = 0;
+ for (RunEntry runEntry : runEntries) {
+ average += runEntry.getGeneration() / runEntries.size();
+ }
+ return average;
+ }
+
+ /**
+ * Calculates the standard deviation of
+ * the average number of generations in
+ * each run.
+ *
+ * @return the standard deviation of the number of generations.
+ */
+ public double getAverageGenerationsStdDev() {
+ double average = getAverageGenerations();
+ double temp, stdDev = 0;
+ for (RunEntry runEntry : runEntries) {
+ temp = runEntry.getGeneration() - average;
+ temp = temp * temp;
+ stdDev += temp;
+ }
+ return stdDev;
+ }
+
+ /**
+ * @return the highest fitness across all runs.
+ */
+ public double getHighestFitness() {
+ double highest = 0;
+ for (RunEntry runEntry : runEntries) {
+ if (runEntry.getFitness() > highest) {
+ highest = runEntry.getFitness();
+ }
+ }
+ return highest;
+ }
+
+ /**
+ * @return the lowest fitness across all runs.
+ */
+ public double getLowestFitness() {
+ double lowest = Double.MAX_VALUE;
+ for (RunEntry runEntry : runEntries) {
+ if (runEntry.getFitness() < lowest) {
+ lowest = runEntry.getFitness();
+ }
+ }
+ return lowest;
+ }
+
+ /**
+ *
+ * @return the number of runs in which a perfect solution was found.
+ */
+ public int getSuccessfulRuns() {
+ int count = 0;
+ for (RunEntry runEntry : runEntries) {
+ if (runEntry.isSuccessful()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Calculates the ratio of successful runs (runs where
+ * a perfect solution was found) to total number of runs.
+ * A double-precision value between 0 and 1 is returned,
+ * where 0 means 0% success rate and 1 means 100% success rate.
+ *
+ * @return the success rate across all runs.
+ */
+ public double getSuccessRate() {
+ return getSuccessfulRuns() / runEntries.size();
+ }
+
+ /**
+ * Calculates the average generation out of successful runs only.
+ * The generation value in each successful run corresponds to the
+ * generation in which the perfect solution was found.
+ *
+ * @return the average number of generations for perfect solutions.
+ */
+ public double getAverageSuccessfulGenerations() {
+ double average = 0;
+ int successfulRuns = getSuccessfulRuns();
+ for (RunEntry runEntry : runEntries) {
+ if (runEntry.isSuccessful()) {
+ average += runEntry.getGeneration() / successfulRuns;
+ }
+ }
+ return average;
+ }
+
+ /**
+ * Calculates the standard deviation of
+ * the average number of generations in
+ * each run where a perfect solution was found.
+ *
+ * @return the standard deviation of the number of generations in successful runs.
+ */
+ public double getAverageSuccessfulGenerationsStdDev() {
+ double average = getAverageSuccessfulGenerations();
+ double temp, stdDev = 0;
+ for (RunEntry runEntry : runEntries) {
+ if (runEntry.isSuccessful()) {
+ temp = runEntry.getGeneration() - average;
+ temp = temp * temp;
+ stdDev += temp;
+ }
+ }
+ return stdDev;
+ }
+
+}
diff --git a/src/jcgp/backend/tests/TestFunctionSet.java b/src/jcgp/backend/tests/TestFunctionSet.java
index 1910d2a..11d9434 100644
--- a/src/jcgp/backend/tests/TestFunctionSet.java
+++ b/src/jcgp/backend/tests/TestFunctionSet.java
@@ -7,10 +7,10 @@ public class TestFunctionSet extends FunctionSet {
public TestFunctionSet() {
- functionList = new Function[] {
+ registerFunctions(
new Function() {
@Override
- public Object run(Object... args) {
+ public Integer run(Object... args) {
return (Integer) args[0] + (Integer) args[1];
}
@Override
@@ -20,7 +20,7 @@ public class TestFunctionSet extends FunctionSet {
},
new Function() {
@Override
- public Object run(Object... args) {
+ public Integer run(Object... args) {
return (Integer) args[0] - (Integer) args[1];
}
@Override
@@ -30,7 +30,7 @@ public class TestFunctionSet extends FunctionSet {
},
new Function() {
@Override
- public Object run(Object... args) {
+ public Integer run(Object... args) {
return (Integer) args[0] * (Integer) args[1];
}
@Override
@@ -40,7 +40,7 @@ public class TestFunctionSet extends FunctionSet {
},
new Function() {
@Override
- public Object run(Object... args) {
+ public Integer run(Object... args) {
return (Integer) args[0] / (Integer) args[1];
}
@Override
@@ -48,7 +48,6 @@ public class TestFunctionSet extends FunctionSet {
return 2;
}
}
- };
- enableAll();
+ );
}
} \ No newline at end of file
diff --git a/src/jcgp/gui/GUI.java b/src/jcgp/gui/GUI.java
index 91c72e6..8056140 100644
--- a/src/jcgp/gui/GUI.java
+++ b/src/jcgp/gui/GUI.java
@@ -59,7 +59,6 @@ public class GUI extends Application {
private Runnable consoleFlush;
public static void main(String[] args) {
- new GUI();
launch();
}
@@ -117,10 +116,14 @@ public class GUI extends Application {
*
*/
BorderPane leftFrame = new BorderPane();
+ BorderPane experimentLayer = new BorderPane();
populationPane = new PopulationPane(this);
settingsPane = new SettingsPane(this);
+ settingsPane.maxWidthProperty().bind(experimentLayer.widthProperty());
+
+ console.maxHeightProperty().bind(experimentLayer.heightProperty());
HorizontalDragResize.makeDragResizable(settingsPane);
VerticalDragResize.makeDragResizable(console);
@@ -128,8 +131,6 @@ public class GUI extends Application {
leftFrame.setCenter(populationPane);
leftFrame.setBottom(console);
- BorderPane experimentLayer = new BorderPane();
-
experimentLayer.setCenter(leftFrame);
experimentLayer.setRight(settingsPane);
diff --git a/src/jcgp/gui/settings/SettingsPane.java b/src/jcgp/gui/settings/SettingsPane.java
index 7fc5621..b1322e4 100644
--- a/src/jcgp/gui/settings/SettingsPane.java
+++ b/src/jcgp/gui/settings/SettingsPane.java
@@ -24,7 +24,7 @@ import jcgp.backend.modules.es.EvolutionaryStrategy;
import jcgp.backend.modules.mutator.Mutator;
import jcgp.backend.modules.problem.Problem;
import jcgp.backend.modules.problem.TestCaseProblem;
-import jcgp.backend.resources.parameters.Parameter;
+import jcgp.backend.parameters.Parameter;
import jcgp.gui.GUI;
import jcgp.gui.settings.parameters.GUIParameter;
import jcgp.gui.settings.testcase.TestCaseTable;
@@ -208,7 +208,9 @@ public class SettingsPane extends AnchorPane {
public void handle(ActionEvent event) {
jcgp.setProblem(problemCBox.getSelectionModel().getSelectedIndex());
refreshParameters(jcgp.getProblem().getLocalParameters(), problemParameters);
- testCaseTable.close();
+ if (testCaseTable != null) {
+ testCaseTable.close();
+ }
gui.setEvaluating(false);
refreshFunctions();
testCaseControlContainer.getChildren().clear();
@@ -358,12 +360,12 @@ public class SettingsPane extends AnchorPane {
* @param cgp
* @param vb
*/
- private void refreshParameters(Parameter<?>[] newParameters, VBox vb) {
+ private void refreshParameters(ArrayList<Parameter<?>> newParameters, VBox vb) {
parameters.removeAll(vb.getChildren());
vb.getChildren().clear();
if (newParameters != null) {
- for (int i = 0; i < newParameters.length; i++) {
- GUIParameter<?> gp = GUIParameter.create(newParameters[i], this);
+ for (int i = 0; i < newParameters.size(); i++) {
+ GUIParameter<?> gp = GUIParameter.create(newParameters.get(i), this);
parameters.add(gp);
vb.getChildren().add(gp);
}
@@ -406,7 +408,6 @@ public class SettingsPane extends AnchorPane {
}
/**
- *
* @return true if the experiment needs to be reset, false otherwise.
*/
public boolean isResetRequired() {
diff --git a/src/jcgp/gui/settings/parameters/GUIBooleanParameter.java b/src/jcgp/gui/settings/parameters/GUIBooleanParameter.java
index cc7113d..eb669bb 100644
--- a/src/jcgp/gui/settings/parameters/GUIBooleanParameter.java
+++ b/src/jcgp/gui/settings/parameters/GUIBooleanParameter.java
@@ -4,8 +4,8 @@ import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Control;
-import jcgp.backend.resources.parameters.BooleanParameter;
-import jcgp.backend.resources.parameters.ParameterStatus;
+import jcgp.backend.parameters.BooleanParameter;
+import jcgp.backend.parameters.ParameterStatus;
import jcgp.gui.settings.SettingsPane;
/**
diff --git a/src/jcgp/gui/settings/parameters/GUIDoubleParameter.java b/src/jcgp/gui/settings/parameters/GUIDoubleParameter.java
index 777e739..16a4cd4 100644
--- a/src/jcgp/gui/settings/parameters/GUIDoubleParameter.java
+++ b/src/jcgp/gui/settings/parameters/GUIDoubleParameter.java
@@ -7,8 +7,8 @@ import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Control;
import javafx.scene.control.TextField;
-import jcgp.backend.resources.parameters.Parameter;
-import jcgp.backend.resources.parameters.ParameterStatus;
+import jcgp.backend.parameters.Parameter;
+import jcgp.backend.parameters.ParameterStatus;
import jcgp.gui.settings.SettingsPane;
/**
diff --git a/src/jcgp/gui/settings/parameters/GUIIntegerParameter.java b/src/jcgp/gui/settings/parameters/GUIIntegerParameter.java
index 6e8b3f1..e8a9183 100644
--- a/src/jcgp/gui/settings/parameters/GUIIntegerParameter.java
+++ b/src/jcgp/gui/settings/parameters/GUIIntegerParameter.java
@@ -5,8 +5,8 @@ import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Control;
import javafx.scene.control.TextField;
-import jcgp.backend.resources.parameters.Parameter;
-import jcgp.backend.resources.parameters.ParameterStatus;
+import jcgp.backend.parameters.Parameter;
+import jcgp.backend.parameters.ParameterStatus;
import jcgp.gui.settings.SettingsPane;
/**
diff --git a/src/jcgp/gui/settings/parameters/GUIParameter.java b/src/jcgp/gui/settings/parameters/GUIParameter.java
index b675fb5..0a9149f 100644
--- a/src/jcgp/gui/settings/parameters/GUIParameter.java
+++ b/src/jcgp/gui/settings/parameters/GUIParameter.java
@@ -10,11 +10,11 @@ import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
-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;
+import jcgp.backend.parameters.BooleanParameter;
+import jcgp.backend.parameters.DoubleParameter;
+import jcgp.backend.parameters.IntegerParameter;
+import jcgp.backend.parameters.Parameter;
+import jcgp.backend.parameters.ParameterStatus;
import jcgp.gui.GUI;
import jcgp.gui.settings.SettingsPane;
@@ -63,11 +63,13 @@ public abstract class GUIParameter<T> extends HBox {
private T referenceValue;
/**
- * This protected constructor contains the common elements to all GUIParameters
- * and should be invoked by any subclasses using super().
+ * This protected template constructor contains the common elements to all
+ * GUIParameters and should be invoked by any subclasses using super(). It
+ * defers the creation of the parameter {@code Control} object to the subclass
+ * currently being built (which in turn is defined by the factory method).
*
- * @param parameter a Parameter for which to generate a GUIParameter
- * @param sp a reference to the SettingsPane
+ * @param parameter a Parameter for which to generate a GUIParameter.
+ * @param sp a reference to the SettingsPane.
*/
protected GUIParameter(Parameter<T> parameter, final SettingsPane settingsPane) {
this.parameter = parameter;
@@ -78,7 +80,7 @@ public abstract class GUIParameter<T> extends HBox {
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
- name = new Label(parameter.getName());
+ name = new Label(parameter.toString());
// set text width to half of the total width of the GUIParameter
name.prefWidthProperty().bind(widthProperty().divide(2));
@@ -107,9 +109,9 @@ public abstract class GUIParameter<T> extends HBox {
* Use this to create an appropriate GUIParameter from any instance of Parameter,
* rather than manually downcasting the Parameter object every time.
*
- * @param parameter a Parameter for which to generate a GUIParameter
- * @param sp a reference to the SettingsPane
- * @return an appropriate instance of GUIParameter
+ * @param parameter a Parameter for which to generate a GUIParameter.
+ * @param sp a reference to the SettingsPane.
+ * @return an appropriate instance of GUIParameter.
*/
public static GUIParameter<?> create(Parameter<?> parameter, SettingsPane sp) {
if (parameter instanceof IntegerParameter) {
diff --git a/src/jcgp/gui/settings/testcase/TestCaseTable.java b/src/jcgp/gui/settings/testcase/TestCaseTable.java
index d7b2e2b..d4c1ff9 100644
--- a/src/jcgp/gui/settings/testcase/TestCaseTable.java
+++ b/src/jcgp/gui/settings/testcase/TestCaseTable.java
@@ -16,6 +16,7 @@ import javafx.stage.WindowEvent;
import javafx.util.Callback;
import jcgp.backend.modules.problem.TestCaseProblem;
import jcgp.backend.modules.problem.TestCaseProblem.TestCase;
+import jcgp.backend.resources.Resources;
import jcgp.gui.GUI;
/**
@@ -31,14 +32,16 @@ public class TestCaseTable extends Stage {
public TestCaseTable(final TestCaseProblem<Object> testCaseProblem, final GUI gui) {
super();
+ Resources resources = gui.getExperiment().getResources();
+
table = new TableView<TestCase<Object>>();
ObservableList<TestCase<Object>> testCaseList = testCaseProblem.getTestCases();
- ArrayList<TableColumn<TestCase<Object>, String>> inputs = new ArrayList<TableColumn<TestCase<Object>, String>>(testCaseProblem.getInputCount());
- ArrayList<TableColumn<TestCase<Object>, String>> outputs = new ArrayList<TableColumn<TestCase<Object>, String>>(testCaseProblem.getOutputCount());
+ ArrayList<TableColumn<TestCase<Object>, String>> inputs = new ArrayList<TableColumn<TestCase<Object>, String>>(resources.inputs());
+ ArrayList<TableColumn<TestCase<Object>, String>> outputs = new ArrayList<TableColumn<TestCase<Object>, String>>(resources.outputs());
TableColumn<TestCase<Object>, String> tc;
- for (int i = 0; i < testCaseProblem.getInputCount(); i++) {
+ for (int i = 0; i < resources.inputs(); i++) {
tc = new TableColumn<TestCase<Object>, String>("I: " + i);
inputs.add(tc);
final int index = i;
@@ -49,10 +52,10 @@ public class TestCaseTable extends Stage {
}
});
tc.setSortable(false);
- tc.prefWidthProperty().bind(table.widthProperty().divide(testCaseProblem.getInputCount() + testCaseProblem.getOutputCount()));
+ tc.prefWidthProperty().bind(table.widthProperty().divide(resources.inputs() + resources.outputs()));
}
- for (int o = 0; o < testCaseProblem.getOutputCount(); o++) {
+ for (int o = 0; o < resources.outputs(); o++) {
tc = new TableColumn<TestCase<Object>, String>("O: " + o);
outputs.add(tc);
final int index = o;
@@ -63,7 +66,7 @@ public class TestCaseTable extends Stage {
}
});
tc.setSortable(false);
- tc.prefWidthProperty().bind(table.widthProperty().divide(testCaseProblem.getInputCount() + testCaseProblem.getOutputCount()));
+ tc.prefWidthProperty().bind(table.widthProperty().divide(resources.inputs() + resources.outputs()));
}
table.getColumns().addAll(inputs);