From 36f4393bcc9e55afa2334baa33e603ce839741a1 Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Thu, 1 May 2014 13:05:27 +0100 Subject: Did more commenting, implemented reflection and statistics --- .classpath | 4 + src/jcgp/JCGP.java | 421 +++++++++++++++++---- .../backend/function/DigitalCircuitFunctions.java | 85 ++++- src/jcgp/backend/function/Function.java | 6 +- src/jcgp/backend/function/FunctionSet.java | 64 ++-- .../function/SymbolicRegressionFunctions.java | 21 +- .../function/TravellingSalesmanFunctions.java | 19 +- src/jcgp/backend/function/UnsignedInteger.java | 20 +- src/jcgp/backend/modules/Module.java | 67 +++- .../backend/modules/es/EvolutionaryStrategy.java | 12 +- src/jcgp/backend/modules/es/MuPlusLambda.java | 32 +- .../backend/modules/es/TournamentSelection.java | 33 +- .../backend/modules/mutator/FixedPointMutator.java | 35 +- src/jcgp/backend/modules/mutator/Mutator.java | 10 +- .../modules/mutator/PercentPointMutator.java | 40 +- src/jcgp/backend/modules/mutator/PointMutator.java | 6 +- .../modules/mutator/ProbabilisticMutator.java | 33 +- .../modules/problem/DigitalCircuitProblem.java | 43 ++- src/jcgp/backend/modules/problem/Problem.java | 142 +++++-- .../modules/problem/SymbolicRegressionProblem.java | 72 +++- .../backend/modules/problem/TestCaseProblem.java | 148 +++++--- .../modules/problem/TravellingSalesmanProblem.java | 35 +- src/jcgp/backend/parameters/BooleanParameter.java | 79 ++++ src/jcgp/backend/parameters/DoubleParameter.java | 79 ++++ src/jcgp/backend/parameters/IntegerParameter.java | 79 ++++ src/jcgp/backend/parameters/Parameter.java | 128 +++++++ src/jcgp/backend/parameters/ParameterStatus.java | 53 +++ .../parameters/monitors/BooleanMonitor.java | 44 +++ .../backend/parameters/monitors/DoubleMonitor.java | 45 +++ .../parameters/monitors/IntegerMonitor.java | 44 +++ src/jcgp/backend/resources/Console.java | 31 ++ .../backend/resources/ModifiableResources.java | 143 ++++--- src/jcgp/backend/resources/Resources.java | 161 ++++++-- .../resources/parameters/BooleanParameter.java | 23 -- .../resources/parameters/DoubleParameter.java | 21 - .../resources/parameters/IntegerParameter.java | 21 - .../backend/resources/parameters/Parameter.java | 57 --- .../resources/parameters/ParameterStatus.java | 16 - src/jcgp/backend/statistics/RunEntry.java | 62 +++ src/jcgp/backend/statistics/StatisticsLogger.java | 233 ++++++++++++ src/jcgp/backend/tests/TestFunctionSet.java | 13 +- src/jcgp/gui/GUI.java | 7 +- src/jcgp/gui/settings/SettingsPane.java | 13 +- .../settings/parameters/GUIBooleanParameter.java | 4 +- .../settings/parameters/GUIDoubleParameter.java | 4 +- .../settings/parameters/GUIIntegerParameter.java | 4 +- src/jcgp/gui/settings/parameters/GUIParameter.java | 28 +- src/jcgp/gui/settings/testcase/TestCaseTable.java | 15 +- 48 files changed, 2100 insertions(+), 655 deletions(-) create mode 100644 src/jcgp/backend/parameters/BooleanParameter.java create mode 100644 src/jcgp/backend/parameters/DoubleParameter.java create mode 100644 src/jcgp/backend/parameters/IntegerParameter.java create mode 100644 src/jcgp/backend/parameters/Parameter.java create mode 100644 src/jcgp/backend/parameters/ParameterStatus.java create mode 100644 src/jcgp/backend/parameters/monitors/BooleanMonitor.java create mode 100644 src/jcgp/backend/parameters/monitors/DoubleMonitor.java create mode 100644 src/jcgp/backend/parameters/monitors/IntegerMonitor.java delete mode 100644 src/jcgp/backend/resources/parameters/BooleanParameter.java delete mode 100644 src/jcgp/backend/resources/parameters/DoubleParameter.java delete mode 100644 src/jcgp/backend/resources/parameters/IntegerParameter.java delete mode 100644 src/jcgp/backend/resources/parameters/Parameter.java delete mode 100644 src/jcgp/backend/resources/parameters/ParameterStatus.java create mode 100644 src/jcgp/backend/statistics/RunEntry.java create mode 100644 src/jcgp/backend/statistics/StatisticsLogger.java diff --git a/.classpath b/.classpath index 3e0fb27..591a691 100644 --- a/.classpath +++ b/.classpath @@ -3,5 +3,9 @@ + + + + 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. - *
- * 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. + *

+ * 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. + *

+ * 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()}. + *

+ * 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 mutators; + private Mutator mutator; // evolutionary algorithms - private EvolutionaryStrategy[] evolutionaryStrategies = new EvolutionaryStrategy[] { - new MuPlusLambda(resources), - new TournamentSelection(resources)}; - private EvolutionaryStrategy evolutionaryStrategy = evolutionaryStrategies[0]; + private ArrayList 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 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. + *

+ * 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> esList = reflections.getSubTypesOf(EvolutionaryStrategy.class); + evolutionaryStrategies = new ArrayList(); + + // go through all subclasses of EvolutionaryStrategy + for (Class esType : esList) { + Constructor 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> mutatorList = reflections.getSubTypesOf(Mutator.class); + mutators = new ArrayList(); + + // go through all subclasses of Mutator + for (Class mutatorType : mutatorList) { + Constructor 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> problemTypes = reflections.getSubTypesOf(Problem.class); + problems = new ArrayList(); + + // go through all subclasses of Problem + for (Class problemType : problemTypes) { + + Constructor 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.
+ * 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 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 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 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. + *
+ * If the experiment is to continue, a new generation is created + * using the selected evolutionary strategy and mutator. + *

+ * 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). + *
+ * 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. + *
+ * 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. + *
+ * 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. *

* 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. *

- * 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 allowedFunctions; - protected String name; + private ArrayList functionList = new ArrayList(); + private ArrayList allowedFunctions = new ArrayList(); /** * @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 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(); - 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. + *
+ * 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. + *
+ * 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. - * + *

+ * TODO in the unlikely event that unsigned integers are natively + * implemented in Java, they should be used instead of this class. + *

+ * Why are unsigned integers not supported?
+ * 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. + *
+ * In addition, implementations of {@code Module} should specify a module name + * in their constructor using {@code setName()}. If a name is not provided, + * the simple name of the class will be used. * * @see Parameter - * * @author Eduardo Pedroni * */ -public interface Module { +public abstract class Module { + private ArrayList> localParameters = new ArrayList>(); + private String name = getClass().getSimpleName(); + /** + * This method is used by the GUI in order to build visual + * representations of all parameters used by the module. + * Therefore, any parameters returned here will be displayed + * visually. + * * @return a list of generic parameters exposed by the module. */ - public abstract Parameter[] getLocalParameters(); + public final ArrayList> getLocalParameters() { + return localParameters; + } + + /** + * Adds one or more parameters to the list of local parameters. + * The same parameter cannot be added twice. + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ * 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. + *

+ * 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 { + + /** + * 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. + *

+ * 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. + *

+ * 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 { + + /** + * 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. + *

+ * 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. + *

+ * 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 { + + /** + * 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. + *

+ * 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. + *

+ * {@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. + *

+ * 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 the data type stored in the parameter. + */ +public abstract class Parameter { + + private boolean monitor, critical; + protected ParameterStatus status = ParameterStatus.VALID; + protected String name; + protected Property 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 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. + *
+ *
    + *
  • INVALID: the new parameter value is not valid, + * and the experiment will not be allowed to run.
  • + *
  • WARNING: the new parameter value is technically valid, + * though it might lead to undesirable behaviour.
  • + *
  • WARNING_RESET: the new parameter value is technically valid + * but will require a reset.
  • + *
  • VALID: the new value is valid.
  • + *

    + * 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). + *
    + * 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. + *

    + * 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. + *

    + * 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()}. + *

    + * 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. + *
    + * 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. + *
    + * 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). + *
    + * 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). + *
    + * 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 { - - 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 { - - 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 { - - 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 { - - protected boolean monitor, critical, reset = false; - - protected ParameterStatus status = ParameterStatus.VALID; - - protected String name; - - protected Property 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 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. + *

    + * 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. + *

    + * 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. + *

    + * {@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 runEntries; + + /** + * Create a new statistics logger, use this when resetting is necessary. + */ + public StatisticsLogger() { + runEntries = new ArrayList(); + } + + /** + * 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. + *

    + * 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> 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 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 parameter, final SettingsPane settingsPane) { this.parameter = parameter; @@ -78,7 +80,7 @@ public abstract class GUIParameter 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 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 testCaseProblem, final GUI gui) { super(); + Resources resources = gui.getExperiment().getResources(); + table = new TableView>(); ObservableList> testCaseList = testCaseProblem.getTestCases(); - ArrayList, String>> inputs = new ArrayList, String>>(testCaseProblem.getInputCount()); - ArrayList, String>> outputs = new ArrayList, String>>(testCaseProblem.getOutputCount()); + ArrayList, String>> inputs = new ArrayList, String>>(resources.inputs()); + ArrayList, String>> outputs = new ArrayList, String>>(resources.outputs()); TableColumn, String> tc; - for (int i = 0; i < testCaseProblem.getInputCount(); i++) { + for (int i = 0; i < resources.inputs(); i++) { tc = new TableColumn, 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, 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); -- cgit v1.2.3