diff options
author | Eduardo Pedroni <ep625@york.ac.uk> | 2014-05-06 14:29:37 +0100 |
---|---|---|
committer | Eduardo Pedroni <ep625@york.ac.uk> | 2014-05-06 14:29:37 +0100 |
commit | 8189116ea4b5db4675e31dfd04a5687d55e29262 (patch) | |
tree | c1815021452a888f8838f1628d8fb4689777e73e /src | |
parent | aa9e74e7f67789f6353fc26e02ee8e68e40609a2 (diff) |
Added javadocs, made minor changes to the comments
Diffstat (limited to 'src')
43 files changed, 500 insertions, 363 deletions
diff --git a/src/jcgp/JCGP.java b/src/jcgp/JCGP.java index 213dd06..898f229 100644 --- a/src/jcgp/JCGP.java +++ b/src/jcgp/JCGP.java @@ -38,7 +38,6 @@ import org.reflections.Reflections; * and must implement jcgp.resources.Console. * * @author Eduardo Pedroni - * @see Resources, Module */ public class JCGP { @@ -121,7 +120,7 @@ public class JCGP { * 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. + * and must contain a constructor with a {@code Resources} object as its only argument. */ private void createEvolutionaryStrategyList() { Set<Class<? extends EvolutionaryStrategy>> esList = reflections.getSubTypesOf(EvolutionaryStrategy.class); @@ -159,7 +158,7 @@ public class JCGP { * 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. + * and must contain a constructor with a {@code Resources} object as its only argument. */ private void createMutatorList() { Set<Class<? extends Mutator>> mutatorList = reflections.getSubTypesOf(Mutator.class); @@ -198,7 +197,7 @@ public class JCGP { * 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. + * and must contain a constructor with a {@code Resources} object as its only argument. */ private void createProblemList() { Set<Class<? extends Problem>> problemTypes = reflections.getSubTypesOf(Problem.class); @@ -236,7 +235,7 @@ public class JCGP { } /** - * Returns a reference to the ModifiableResources used by the + * Returns a reference to the {@code ModifiableResources} used by the * experiment. <br> * Use this with care, since changing experiment parameters may * have unintended effects if not done properly. @@ -303,7 +302,7 @@ public class JCGP { /** - * @param mutator the index of the desired mutator. + * @param index the index of the desired mutator. */ public void setMutator(int index) { this.mutator = mutators.get(index); @@ -312,7 +311,7 @@ public class JCGP { /** - * @param evolutionaryStrategy the index of the desired evolutionary strategy. + * @param index the index of the desired evolutionary strategy. */ public void setEvolutionaryStrategy(int index) { this.evolutionaryStrategy = evolutionaryStrategies.get(index); @@ -321,11 +320,12 @@ public class JCGP { /** - * @param problem the index of the desired problem type. + * @param index the index of the desired problem type. */ public void setProblem(int index) { this.problem = problems.get(index); resources.setFunctionSet(problem.getFunctionSet()); + resources.setFitnessOrientation(problem.getFitnessOrientation()); } /** @@ -348,13 +348,14 @@ public class JCGP { if (resources.currentGeneration() < resources.generations()) { // we still have generations left to go - if (problem.isPerfectSolution(population.getFittest())) { + int perfect = problem.perfectSolutionFound(population); + if (perfect >= 0) { // log results - statistics.logRun(resources.currentGeneration(), population.getFittest().getFitness(), population.getFittest().getActiveNodes().size(), true); + statistics.logRun(resources.currentGeneration(), population.get(perfect).getFitness(), population.get(perfect).getActiveNodes().size(), true); resetStatisticsValues(); // solution has been found, start next run - resources.println("[CGP] Solution found, generation " + resources.currentGeneration() + ", chromosome " + population.getFittestIndex() + "\n"); + resources.println("[CGP] Solution found: generation " + resources.currentGeneration() + ", chromosome " + perfect + "\n"); if (resources.currentRun() < resources.runs()) { // there are still runs left @@ -369,17 +370,23 @@ public class JCGP { finished = true; } } else { - if (problem.isImprovement(population.getFittest())) { - printImprovement(); + // solution not found, look for improvement + int improvement = problem.hasImprovement(population); + + if (improvement >= 0) { + // there has been improvement, print it + printImprovement(improvement); lastImprovementGeneration = resources.currentGeneration(); - bestFitnessFound = population.getFittest().getFitness(); - activeNodes = population.getFittest().getActiveNodes().size(); + bestFitnessFound = population.get(improvement).getFitness(); + activeNodes = population.get(improvement).getActiveNodes().size(); } else { + // there has been no improvement, report generation reportGeneration(); } resources.incrementGeneration(); - // solution isn't perfect and we still have generations left, evolve more! - evolutionaryStrategy.evolve(population, mutator, (Resources) resources); + + // we still have generations left, evolve more! + evolutionaryStrategy.evolve(population, mutator); } } else { // the run has ended, tell the user and log it @@ -436,9 +443,9 @@ public class JCGP { * Used internally for reporting improvement, which happens independently of * the report interval parameter. */ - private void printImprovement() { + private void printImprovement(int chromosome) { resources.println("[CGP] Generation: " + resources.currentGeneration() + ", fittest chromosome (" - + population.getFittestIndex() + ") has fitness: " + population.getFittest().getFitness()); + + chromosome + ") has fitness: " + population.get(chromosome).getFitness()); } /** @@ -446,8 +453,8 @@ public class JCGP { * by the report interval parameter. */ private void reportGeneration() { - resources.reportln("[CGP] Generation: " + resources.currentGeneration() + ", fittest chromosome (" - + population.getFittestIndex() + ") has fitness: " + population.getFittest().getFitness()); + resources.reportln("[CGP] Generation: " + resources.currentGeneration() + ", best fitness: " + + problem.getBestFitness()); } /** @@ -545,7 +552,7 @@ public class JCGP { * @param chromosomeIndex the population index into which to parse. */ public void loadChromosome(File file, int chromosomeIndex) { - ChromosomeParser.parse(file, population.getChromosome(chromosomeIndex), resources); + ChromosomeParser.parse(file, population.get(chromosomeIndex), resources); } /** @@ -556,7 +563,7 @@ public class JCGP { * @param chromosomeIndex the index of the chromosome to save. */ public void saveChromosome(File file, int chromosomeIndex) { - ChromosomeParser.save(file, population.getChromosome(chromosomeIndex), resources); + ChromosomeParser.save(file, population.get(chromosomeIndex), resources); } /** diff --git a/src/jcgp/backend/function/DigitalCircuitFunctions.java b/src/jcgp/backend/function/DigitalCircuitFunctions.java index 0d4ae8e..a217eb7 100644 --- a/src/jcgp/backend/function/DigitalCircuitFunctions.java +++ b/src/jcgp/backend/function/DigitalCircuitFunctions.java @@ -5,16 +5,16 @@ package jcgp.backend.function; * (defined as unsigned integer functions in the classic * CGP implementation) defined in static nested classes. * <br> - * This is the function set used by DigitalCircuitProblem. + * This is the function set used by {@code DigitalCircuitProblem}. * - * @see DigitalCircuiProblem + * @see jcgp.backend.modules.problem.DigitalCircuitProblem * @author Eduardo Pedroni * */ public class DigitalCircuitFunctions extends FunctionSet { /** - * Creates a new instance of DigitalCircuitFunctions. + * Creates a new instance of {@code DigitalCircuitFunctions}. */ public DigitalCircuitFunctions() { registerFunctions( diff --git a/src/jcgp/backend/function/SymbolicRegressionFunctions.java b/src/jcgp/backend/function/SymbolicRegressionFunctions.java index 9c6a3b1..a4750fc 100644 --- a/src/jcgp/backend/function/SymbolicRegressionFunctions.java +++ b/src/jcgp/backend/function/SymbolicRegressionFunctions.java @@ -1,5 +1,7 @@ package jcgp.backend.function; +import jcgp.backend.modules.problem.SymbolicRegressionProblem; + /** * This class contains all symbolic regression functions * (defined as double functions in the classic CGP implementation) @@ -180,7 +182,7 @@ public class SymbolicRegressionFunctions extends FunctionSet { /** * Protected tangent function, in radians. Returns the tangent of input 0. - * If input 0 is less than {@link DoubleArithmetic.}DIVISION_LIMIT, + * If input 0 is less than {@code DoubleArithmetic.DIVISION_LIMIT}, * this returns it unchanged. * * @see Math @@ -317,7 +319,7 @@ public class SymbolicRegressionFunctions extends FunctionSet { /** * Protected natural log function. Returns the natural log of the absolute - * value of input 0. If input 0 is less than {@link DoubleArithmetic.}DIVISION_LIMIT, + * value of input 0. If input 0 is less than {@code DoubleArithmetic.DIVISION_LIMIT}, * this returns it unchanged. * * @see Math @@ -346,7 +348,7 @@ public class SymbolicRegressionFunctions extends FunctionSet { /** * Protected log base 10 function. Returns the log to base 10 the absolute - * value of input 0. If input 0 is less than {@link DoubleArithmetic.}DIVISION_LIMIT, + * value of input 0. If input 0 is less than {@code DoubleArithmetic.DIVISION_LIMIT}, * this returns it unchanged. * * @see Math diff --git a/src/jcgp/backend/function/TravellingSalesmanFunctions.java b/src/jcgp/backend/function/TravellingSalesmanFunctions.java index 06b44bb..8a326b5 100644 --- a/src/jcgp/backend/function/TravellingSalesmanFunctions.java +++ b/src/jcgp/backend/function/TravellingSalesmanFunctions.java @@ -3,9 +3,8 @@ package jcgp.backend.function; /** * This class contains all travelling salesman functions in static nested classes. * <br> - * This is the function set used by TravellingSalesmanProblem. + * This is the function set to be used by TravellingSalesmanProblem. * - * @see TravellingSalesmanProblem * @author Eduardo Pedroni * */ @@ -113,7 +112,7 @@ public class TravellingSalesmanFunctions extends FunctionSet { /** * Scaled exponential function. Returns the exponential of input 0 - * scaled to the range 0 < x < 1. + * scaled to the range 0 < x > 1. * * @see Math */ @@ -228,7 +227,7 @@ public class TravellingSalesmanFunctions extends FunctionSet { /** * Scaled hypotenuse function. Returns the square root of input 0 squared - * plus input 1 squared, scaled to the range 0 < x < 1. + * plus input 1 squared, scaled to the range 0 < x > 1. * * @see Math */ @@ -257,7 +256,7 @@ public class TravellingSalesmanFunctions extends FunctionSet { /** * Scaled addition returns the sum of inputs 0 and 1 scaled - * to the range 0 < x < 1. + * to the range 0 < x > 1. * */ public static class ScaledAddition extends Function { @@ -285,7 +284,7 @@ public class TravellingSalesmanFunctions extends FunctionSet { /** * Symmetric subtraction returns the absolute difference between inputs 0 and 1, - * scaled to the range 0 < x < 1. + * scaled to the range 0 <; x > 1. * */ public static class SymmetricSubtraction extends Function { diff --git a/src/jcgp/backend/modules/Module.java b/src/jcgp/backend/modules/Module.java index b53184e..a4d36c1 100644 --- a/src/jcgp/backend/modules/Module.java +++ b/src/jcgp/backend/modules/Module.java @@ -3,6 +3,7 @@ package jcgp.backend.modules; import java.util.ArrayList; import jcgp.backend.parameters.Parameter; +import jcgp.backend.resources.Resources; /** * This class defines the expected behaviour of a module. Generally, modules @@ -15,6 +16,9 @@ import jcgp.backend.parameters.Parameter; * 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. + * <br> + * All modules, by definition, contain a reference to the experiment's resources, which + * must be passed at construction. The resources should be accessed with {@code getResources()}. * * @see Parameter * @author Eduardo Pedroni @@ -24,6 +28,17 @@ public abstract class Module { private ArrayList<Parameter<?>> localParameters = new ArrayList<Parameter<?>>(); private String name = getClass().getSimpleName(); + private Resources resources; + + /** + * Makes a new instance of this class. This should never + * be called directly, and should instead be used by subclasses. + * + * @param resources a reference to the experiment's resources. + */ + protected Module(Resources resources) { + this.resources = resources; + } /** * This method is used by the GUI in order to build visual @@ -66,6 +81,13 @@ public abstract class Module { this.name = name; } + /** + * @return the experiment's resources. + */ + protected Resources getResources() { + return resources; + } + @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 1a14552..30a3c4a 100644 --- a/src/jcgp/backend/modules/es/EvolutionaryStrategy.java +++ b/src/jcgp/backend/modules/es/EvolutionaryStrategy.java @@ -33,12 +33,21 @@ import jcgp.backend.resources.Resources; public abstract class EvolutionaryStrategy extends Module { /** + * For internal use only, initialises the resources field. + * + * @param resources the experiment's resources. + */ + protected EvolutionaryStrategy(Resources resources) { + super(resources); + } + + /** * Performs the selection algorithm and uses the mutator to create * the next generation of solutions. * * @param population the population to evolve. * @param mutator the mutator with which to mutate the promoted individuals. - * @param resources parameters and utilities for optional reference. */ - public abstract void evolve(Population population, Mutator mutator, Resources resources); + public abstract void evolve(Population population, Mutator mutator); + } diff --git a/src/jcgp/backend/modules/es/MuPlusLambda.java b/src/jcgp/backend/modules/es/MuPlusLambda.java index 8186b11..3743ee6 100644 --- a/src/jcgp/backend/modules/es/MuPlusLambda.java +++ b/src/jcgp/backend/modules/es/MuPlusLambda.java @@ -28,21 +28,21 @@ import jcgp.backend.resources.Resources; * */ public class MuPlusLambda extends EvolutionaryStrategy { - + private IntegerParameter mu, lambda; private BooleanParameter report; - + /** * Creates a new instance of MuPlusLambda. * * @param resources a reference to the experiment's resources. */ public MuPlusLambda(final Resources resources) { - super(); + super(resources); mu = new IntegerParameter(1, "Parents (μ)") { @Override public void validate(Number newValue) { - if (newValue.intValue() + lambda.get() != resources.populationSize()) { + if (newValue.intValue() + lambda.get() != getResources().populationSize()) { status = ParameterStatus.INVALID; status.setDetails("Parents + offspring must equal population size."); } else if (newValue.intValue() <= 0) { @@ -53,11 +53,11 @@ public class MuPlusLambda extends EvolutionaryStrategy { } } }; - + lambda = new IntegerParameter(4, "Offspring (λ)") { @Override public void validate(Number newValue) { - if (newValue.intValue() + mu.get() != resources.populationSize()) { + if (newValue.intValue() + mu.get() != getResources().populationSize()) { status = ParameterStatus.INVALID; status.setDetails("Parents + offspring must equal population size."); } else if (newValue.intValue() <= 0) { @@ -68,29 +68,30 @@ public class MuPlusLambda extends EvolutionaryStrategy { } } }; - + report = new BooleanParameter(false, "Report"); - + setName("(μ + λ)"); registerParameters(mu, lambda, report); } - + @Override - public void evolve(Population population, Mutator mutator, Resources resources) { - /* Population is sorted in ascending order of fitness, so leave the last - * mu chromosomes intact - */ - for (int i = 0; i < resources.populationSize() - mu.get(); i++) { + public void evolve(Population population, Mutator mutator) { + // sort the population in order of best fitness + population.sort(); + + // population is now sorted in ascending order of fitness, the last chromosomes are the fittest + for (int i = 0; i < getResources().populationSize() - mu.get(); i++) { // select a random parent out of the lambda offspring individuals - int randomParent = resources.populationSize() - 1 - resources.getRandomInt(mu.get()); - if (report.get()) resources.reportln("[ES] Copying Chr " + randomParent + " to population position " + i); + int randomParent = getResources().populationSize() - 1 - getResources().getRandomInt(mu.get()); + if (report.get()) getResources().reportln("[ES] Copying Chr " + randomParent + " to population position " + i); population.copyChromosome(randomParent, i); - + // mutate selected chromosome - if (report.get()) resources.reportln("[ES] Mutating copied chromosome"); - mutator.mutate(population.getChromosome(i), resources); + if (report.get()) getResources().reportln("[ES] Mutating copied chromosome"); + mutator.mutate(population.get(i)); } - - if (report.get()) resources.reportln("[ES] Generation is complete"); + + if (report.get()) getResources().reportln("[ES] Generation is complete"); } } diff --git a/src/jcgp/backend/modules/es/TournamentSelection.java b/src/jcgp/backend/modules/es/TournamentSelection.java index 209caca..6d467f5 100644 --- a/src/jcgp/backend/modules/es/TournamentSelection.java +++ b/src/jcgp/backend/modules/es/TournamentSelection.java @@ -44,7 +44,7 @@ public class TournamentSelection extends EvolutionaryStrategy { * @param resources a reference to the experiment's resources. */ public TournamentSelection(final Resources resources) { - super(); + super(resources); tournamentSize = new IntegerParameter(1, "Tournament size") { @Override public void validate(Number newValue) { @@ -73,17 +73,22 @@ public class TournamentSelection extends EvolutionaryStrategy { } @Override - public void evolve(Population population, Mutator mutator, Resources resources) { + public void evolve(Population population, Mutator mutator) { /* Create an entirely new population by isolating random subsets of * the original population and choosing the fittest individual within * that subset. Each chosen individual is mutated and copied back into the * population. */ - Chromosome[] newPopulation = new Chromosome[resources.populationSize()]; + + // sort the population by fitness to make things easier + population.sort(); + + // this array holds the new population temporarily, until it is copied over + Chromosome[] newPopulation = new Chromosome[getResources().populationSize()]; // start by selecting all of the chromosomes that will be promoted - for (int i = 0; i < resources.populationSize(); i++) { - if (report.get()) resources.reportln("[ES] Starting tournament " + i); + for (int i = 0; i < getResources().populationSize(); i++) { + if (report.get()) getResources().reportln("[ES] Starting tournament " + i); /* the population is sorted in ascending order of fitness, * meaning the higher the index of the contender, the fitter @@ -91,21 +96,21 @@ public class TournamentSelection extends EvolutionaryStrategy { */ int[] contenders = new int[tournamentSize.get()]; for (int t = 0; t < tournamentSize.get() - 1; t++) { - contenders[t] = resources.getRandomInt(resources.populationSize()); + contenders[t] = getResources().getRandomInt(getResources().populationSize()); } - if (report.get()) resources.reportln("[ES] Selected contenders: " + Arrays.toString(contenders)); + if (report.get()) getResources().reportln("[ES] Selected contenders: " + Arrays.toString(contenders)); Arrays.sort(contenders); - if (report.get()) resources.reportln("[ES] Chr " + contenders[contenders.length - 1] + " wins the tournament, copying and mutating..."); + if (report.get()) getResources().reportln("[ES] Chr " + contenders[contenders.length - 1] + " wins the tournament, copying and mutating..."); // create a copy of the selected chromosome and mutate it - newPopulation[i] = new Chromosome(population.getChromosome(contenders[contenders.length - 1])); - mutator.mutate(newPopulation[i], resources); + newPopulation[i] = new Chromosome(population.get(contenders[contenders.length - 1])); + mutator.mutate(newPopulation[i]); } - if (report.get()) resources.reportln("[ES] Tournaments are finished, copying new chromosomes into population"); + if (report.get()) getResources().reportln("[ES] Tournaments are finished, copying new chromosomes into population"); // newPopulation has been generated, copy into the population - for (int c = 0; c < resources.populationSize(); c++) { - population.getChromosome(c).copyGenes(newPopulation[c]); + for (int c = 0; c < getResources().populationSize(); c++) { + population.get(c).copyGenes(newPopulation[c]); } - if (report.get()) resources.reportln("[ES] Generation is complete"); + if (report.get()) getResources().reportln("[ES] Generation is complete"); } } diff --git a/src/jcgp/backend/modules/mutator/FixedPointMutator.java b/src/jcgp/backend/modules/mutator/FixedPointMutator.java index 5d40c57..1efdd2f 100644 --- a/src/jcgp/backend/modules/mutator/FixedPointMutator.java +++ b/src/jcgp/backend/modules/mutator/FixedPointMutator.java @@ -25,7 +25,7 @@ public class FixedPointMutator extends PointMutator { * @param resources a reference to the experiment's resources. */ public FixedPointMutator(final Resources resources) { - super(); + super(resources); genesMutated = new IntegerParameter(5, "Genes mutated", false, false) { @Override public void validate(Number newValue) { diff --git a/src/jcgp/backend/modules/mutator/Mutator.java b/src/jcgp/backend/modules/mutator/Mutator.java index 02fd70a..56692ef 100644 --- a/src/jcgp/backend/modules/mutator/Mutator.java +++ b/src/jcgp/backend/modules/mutator/Mutator.java @@ -25,14 +25,22 @@ import jcgp.backend.resources.Resources; * */ public abstract class Mutator extends Module { + + /** + * For internal use only, initialises the resources field. + * + * @param resources the experiment's resources. + */ + protected Mutator(Resources resources) { + super(resources); + } /** * Applies mutations to the specified chromosome according * to the parameter values. * * @param chromosome the chromosome to mutate. - * @param resources parameters and utilities for optional reference. */ - public abstract void mutate(Chromosome chromosome, Resources resources); + public abstract void mutate(Chromosome chromosome); } diff --git a/src/jcgp/backend/modules/mutator/PercentPointMutator.java b/src/jcgp/backend/modules/mutator/PercentPointMutator.java index 31a7739..015edf4 100644 --- a/src/jcgp/backend/modules/mutator/PercentPointMutator.java +++ b/src/jcgp/backend/modules/mutator/PercentPointMutator.java @@ -24,12 +24,12 @@ public class PercentPointMutator extends PointMutator { private DoubleParameter mutationRate; /** - * Creates a new instance of PointMutator. + * Creates a new instance of PercentPointMutator. * * @param resources a reference to the experiment's resources. */ public PercentPointMutator(final Resources resources) { - super(); + super(resources); mutationRate = new DoubleParameter(10, "Percent mutation", false, false) { @Override public void validate(Number newValue) { diff --git a/src/jcgp/backend/modules/mutator/PointMutator.java b/src/jcgp/backend/modules/mutator/PointMutator.java index 0223a37..9e421c2 100644 --- a/src/jcgp/backend/modules/mutator/PointMutator.java +++ b/src/jcgp/backend/modules/mutator/PointMutator.java @@ -24,48 +24,57 @@ public abstract class PointMutator extends Mutator { protected IntegerParameter genesMutated; protected BooleanParameter report; + + /** + * For internal use only, initialises the resources field. + * + * @param resources the experiment's resources. + */ + protected PointMutator(Resources resources) { + super(resources); + } @Override - public void mutate(Chromosome chromosome, Resources resources) { - if (report.get()) resources.reportln("[Mutator] Number of mutations to be performed: " + genesMutated.get()); + public void mutate(Chromosome chromosome) { + if (report.get()) getResources().reportln("[Mutator] Number of mutations to be performed: " + genesMutated.get()); // for however many genes must be mutated for (int i = 0; i < genesMutated.get(); i++) { MutableElement m = chromosome.getRandomMutableElement(); - if (report.get()) resources.report("[Mutator] Mutation " + i + " selected " + m + ", "); + if (report.get()) getResources().report("[Mutator] Mutation " + i + " selected " + m + ", "); // outputs and nodes are mutated differently if (m instanceof Output) { - if (report.get()) resources.report("changed source from " + ((Output) m).getSource() + " "); + if (report.get()) getResources().report("changed source from " + ((Output) m).getSource() + " "); // outputs are easy, simply set to a different random connection, any will do m.setConnection(0, chromosome.getRandomConnection()); - if (report.get()) resources.reportln("to " + ((Output) m).getSource()); + if (report.get()) getResources().reportln("to " + ((Output) m).getSource()); } else if (m instanceof Node) { /* nodes are more complicated, first we must decide whether to mutate the function * or a connection * we do this by generating a random int between 0 and 1 + arity */ - int geneType = resources.getRandomInt(1 + resources.arity()); + int geneType = getResources().getRandomInt(1 + getResources().arity()); // if the int is less than 1, mutate function, else mutate connections if (geneType < 1) { - if (report.get()) resources.report("changed function from " + ((Node) m).getFunction() + " "); + if (report.get()) getResources().report("changed function from " + ((Node) m).getFunction() + " "); - ((Node) m).setFunction(resources.getRandomFunction()); + ((Node) m).setFunction(getResources().getRandomFunction()); - if (report.get()) resources.reportln("to " + ((Node) m).getFunction()); + if (report.get()) getResources().reportln("to " + ((Node) m).getFunction()); } else { // if we decided to mutate connection, subtract 1 from geneType so it fits into the arity range geneType -= 1; - if (report.get()) resources.report("changed connection " + geneType + " from " + ((Node) m).getConnection(geneType) + " "); + if (report.get()) getResources().report("changed connection " + geneType + " from " + ((Node) m).getConnection(geneType) + " "); m.setConnection(geneType, chromosome.getRandomConnection(((Node) m).getColumn())); - if (report.get()) resources.reportln("to " + ((Node) m).getConnection(geneType)); + if (report.get()) getResources().reportln("to " + ((Node) m).getConnection(geneType)); } } } diff --git a/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java b/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java index c65fc22..9273558 100644 --- a/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java +++ b/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java @@ -25,17 +25,14 @@ public class ProbabilisticMutator extends Mutator { private DoubleParameter mutationProbability; private BooleanParameter report; - - private Resources resources; - + /** * Creates a new instance of ProbabilisticMutator. * * @param resources a reference to the experiment's resources. */ public ProbabilisticMutator(Resources resources) { - super(); - this.resources = resources; + super(resources); mutationProbability = new DoubleParameter(10, "Mutation probability", false, false) { @Override @@ -56,53 +53,53 @@ public class ProbabilisticMutator extends Mutator { } @Override - public void mutate(Chromosome chromosome, Resources resources) { - if (report.get()) resources.reportln("[Mutator] Starting mutations"); + public void mutate(Chromosome chromosome) { + if (report.get()) getResources().reportln("[Mutator] Starting mutations"); // go through nodes - [rows][columns] - for (int r = 0; r < resources.rows(); r++) { - for (int c = 0; c < resources.columns(); c++) { + for (int r = 0; r < getResources().rows(); r++) { + for (int c = 0; c < getResources().columns(); c++) { // go through all connections - for (int a = 0; a < resources.arity(); a++) { + for (int a = 0; a < getResources().arity(); a++) { if (mutateGene()) { Node n = chromosome.getNode(r, c); - if (report.get()) resources.report("[Mutator] Mutating " + n + + if (report.get()) getResources().report("[Mutator] Mutating " + n + ", changed connection " + a + " from " + n.getConnection(a) + " "); n.setConnection(a, chromosome.getRandomConnection(c)); - if (report.get()) resources.reportln("to " + n.getConnection(a)); + if (report.get()) getResources().reportln("to " + n.getConnection(a)); } } // deal with node function next if (mutateGene()) { Node n = chromosome.getNode(r, c); - if (report.get()) resources.report("[Mutator] Mutating " + n + + if (report.get()) getResources().report("[Mutator] Mutating " + n + ", changed function from " + n.getFunction()); - n.setFunction(resources.getRandomFunction()); + n.setFunction(getResources().getRandomFunction()); - if (report.get()) resources.reportln(" to " + n.getFunction()); + if (report.get()) getResources().reportln(" to " + n.getFunction()); } } } // finally, mutate outputs - for (int o = 0; o < resources.outputs(); o++) { + for (int o = 0; o < getResources().outputs(); o++) { if (mutateGene()) { Output out = chromosome.getOutput(o); - if (report.get()) resources.report("[Mutator] Mutating " + out + + if (report.get()) getResources().report("[Mutator] Mutating " + out + ", changed source from " + out.getSource()); out.setConnection(0, chromosome.getRandomConnection()); - if (report.get()) resources.reportln("to " + out.getSource()); + if (report.get()) getResources().reportln("to " + out.getSource()); } } - if (report.get()) resources.reportln("[Mutator] Mutation finished "); + if (report.get()) getResources().reportln("[Mutator] Mutation finished "); } @@ -115,6 +112,6 @@ public class ProbabilisticMutator extends Mutator { * @return true if a mutation should be performed, false if otherwise. */ private boolean mutateGene() { - return resources.getRandomDouble(100) < mutationProbability.get(); + return getResources().getRandomDouble(100) < mutationProbability.get(); } } diff --git a/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java b/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java index 6654928..0071ed5 100644 --- a/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java +++ b/src/jcgp/backend/modules/problem/DigitalCircuitProblem.java @@ -2,7 +2,6 @@ package jcgp.backend.modules.problem; import jcgp.backend.function.DigitalCircuitFunctions; import jcgp.backend.function.UnsignedInteger; -import jcgp.backend.population.Chromosome; import jcgp.backend.population.Population; import jcgp.backend.resources.Resources; @@ -18,7 +17,7 @@ import jcgp.backend.resources.Resources; * */ public class DigitalCircuitProblem extends TestCaseProblem<UnsignedInteger> { - + /** * Construct a new instance of DigitalCircuitProblem. * @@ -30,43 +29,38 @@ public class DigitalCircuitProblem extends TestCaseProblem<UnsignedInteger> { setName("Digital circuit"); setFileExtension(".plu"); } - + @Override public void evaluate(Population population, Resources resources) { // for every chromosome in the population for (int i = 0; i < resources.populationSize(); i++) { // assume an initial fitness of 0 int fitness = 0; - + // iterate over every test case for (int t = 0; t < testCases.size(); t++) { - population.getChromosome(i).setInputs((Object[]) testCases.get(t).getInputs()); + population.get(i).setInputs((Object[]) testCases.get(t).getInputs()); // check each output for (int o = 0; o < resources.outputs(); o++) { - Integer output = ((UnsignedInteger) population.getChromosome(i).getOutput(o).calculate()).get(); + Integer output = ((UnsignedInteger) population.get(i).getOutput(o).calculate()).get(); Integer matches = ~(output ^ testCases.get(t).getOutput(o).get()); // check only the relevant bits int bits = (int) Math.pow(2.0, (double) resources.inputs()); for (int b = 0; b < bits; b++) { - if (((matches >>> b) & 1) == 1) { - fitness++; - } + fitness += (matches >>> b) & 1; } } } - + // assign the resulting fitness to the respective individual - population.getChromosome(i).setFitness(fitness); + population.get(i).setFitness(fitness); } - - // sort population - population.sortAscending(); } - + @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(); + double maxFitness = Math.pow(2.0, (double) getResources().inputs()) * getResources().outputs(); return maxFitness; } @@ -81,24 +75,30 @@ public class DigitalCircuitProblem extends TestCaseProblem<UnsignedInteger> { for (int o = 0; o < outputCases.length; o++) { outputCases[o] = new UnsignedInteger(outputs[o]); } - + return new TestCase<UnsignedInteger>(inputCases, outputCases); } - + @Override - public boolean isPerfectSolution(Chromosome fittest) { + public int perfectSolutionFound(Population population) { // higher fitness is better - return fittest.getFitness() >= maxFitness.get(); + for (int i = 0; i < getResources().populationSize(); i++) { + if (population.get(i).getFitness() >= maxFitness.get()) { + return i; + } + } + return -1; } @Override - public boolean isImprovement(Chromosome fittest) { + public int hasImprovement(Population population) { // higher fitness is better - if (fittest.getFitness() > bestFitness.get()) { - bestFitness.set(fittest.getFitness()); - return true; - } else { - return false; + for (int i = 0; i < getResources().populationSize(); i++) { + if (population.get(i).getFitness() > bestFitness.get()) { + bestFitness.set(population.get(i).getFitness()); + return i; + } } + return -1; } } diff --git a/src/jcgp/backend/modules/problem/Problem.java b/src/jcgp/backend/modules/problem/Problem.java index 721b9b3..5f194b5 100644 --- a/src/jcgp/backend/modules/problem/Problem.java +++ b/src/jcgp/backend/modules/problem/Problem.java @@ -6,23 +6,21 @@ 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; import jcgp.backend.resources.Resources; /** - * Defines the general behaviour of a CGP problem. The primary function of Problem + * Defines the general behaviour of a CGP problem. The primary function of {@code Problem} * is to evaluate a population and assign a fitness value to each chromosome. * <br> - * By convention, the population should be sorted into ascending order of fitness. The - * reason for this is because high fitness is not necessarily better - some problem types - * might treat 0 as the best fitness. In order for the evolutionary strategy to be able to - * pick chromosomes by fitness, the safest way is to sort them such that the last chromosome - * is the fittest. + * Problems are free to define whether better fitness means a higher or lower fitness value. + * In some problem types, it is more convenient to treat fitness 0 as the best possible value. + * This can be done by changing the fitness orientation to {@code BestFitness.HIGH} or {@code BestFitness.LOW} as appropriate. + * Fitness orientation is set to high by default. * <br><br> - * When extending this class, the constructor should call a couple methods in order to - * properly construct the problem type: {@code setFunctionSet()} and {@code setFileExtension()}, + * When extending this class, the constructor should call a few methods in order to + * properly construct the problem type: {@code setFunctionSet()}, {@code setFileExtension()} and {@code setFitnessOrientation()}, * with the respective arguments. As with all subclasses of {@code Module}, {@code setName()} and * {@code registerParameters()} should be used where appropriate as well. * <br><br> @@ -41,12 +39,18 @@ public abstract class Problem extends Module { private FunctionSet functionSet; private String fileExtension = ".*"; + private BestFitness fitnessOrientation = BestFitness.HIGH; + protected DoubleParameter maxFitness, bestFitness; /** * Initialises the two problem-wide parameters, maxFitness and bestFitness. + * + * @param resources a reference to the experiment's resources. */ - public Problem() { + public Problem(Resources resources) { + super(resources); + maxFitness = new DoubleMonitor(0, "Max fitness"); bestFitness = new DoubleMonitor(0, "Best fitness"); registerParameters(maxFitness, bestFitness); @@ -73,29 +77,26 @@ public abstract class Problem extends Module { public abstract void evaluate(Population population, Resources resources); /** - * Used to assert whether a given chromosome is a perfect solution + * Used to assert whether a given population contains a perfect solution * to the problem. It is up to the problem to define what qualifies * a perfect solution, as some problems (subject ones such as music and * art evolution, for example) might not have perfect solutions at all. - * <br><br> - * Note that if this method returns true, the experiment will move on - * to the next run, or finish if no more runs are left. * - * @param candidate the potentially perfect chromosome. - * @return true if the argument is a perfect solution. + * @param population the population to search through for a perfect chromosome. + * @return the perfect solution index, if one exits, -1 if no perfect solution was found. */ - public abstract boolean isPerfectSolution(Chromosome candidate); + public abstract int perfectSolutionFound(Population population); /** - * Used to assert whether a given chromosome is an improvement over + * Used to assert whether a given population has a chromosome that 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. + * @param population the population potentially containing a fitter chromosome. + * @return the index of the first chromosome in the population that is an improvement, -1 if none is found. */ - public abstract boolean isImprovement(Chromosome candidate); + public abstract int hasImprovement(Population population); /** * Parses the specified file and uses the parsed data to @@ -149,6 +150,20 @@ public abstract class Problem extends Module { } /** + * @param newOrientation the new fitness orientation to set. + */ + protected void setFitnessOrientation(BestFitness newOrientation) { + this.fitnessOrientation = newOrientation; + } + + /** + * @return the fitness orientation of this particular problem. + */ + public BestFitness getFitnessOrientation() { + return fitnessOrientation; + } + + /** * @return the current best fitness, in other words, the fitness * value of the fittest chromosome in the current generation. */ diff --git a/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java b/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java index 04e9fe8..24c61d6 100644 --- a/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java +++ b/src/jcgp/backend/modules/problem/SymbolicRegressionProblem.java @@ -4,7 +4,6 @@ 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; @@ -20,12 +19,12 @@ import jcgp.backend.resources.Resources; * <li>Error threshold: the maximum difference allowed between an * evolved output and the equivalent output from the problem data. * Outputs within the error threshold will be considered correct. - * This is only used if HITS is enabled.</li> + * This is only used if hits is enabled.</li> * <li>Perfection threshold: if the fitness is calculated without - * using the HITS method, it is a decimal value. A solution is + * using the hits method, it is a decimal value. A solution is * considered perfect when the difference between its fitness and * the maximum possible fitness is within the perfection threshold.</li> - * <li>HITS-based fitness: increment the fitness by 1 whenever the + * <li>Hits-based fitness: increment the fitness by 1 whenever the * chromosome output is within the error threshold.</li></ul> * * @@ -79,7 +78,7 @@ public class SymbolicRegressionProblem extends TestCaseProblem<Double> { } }; - hitsBasedFitness = new BooleanParameter(true, "HITS-based fitness"); + hitsBasedFitness = new BooleanParameter(false, "Hits-based fitness"); registerParameters(errorThreshold, perfectionThreshold, hitsBasedFitness); } @@ -87,15 +86,15 @@ public class SymbolicRegressionProblem extends TestCaseProblem<Double> { @Override public void evaluate(Population population, Resources resources) { // for every chromosome in the population - for (int i = 0; i < resources.populationSize(); i++) { + for (int i = 0; i < getResources().populationSize(); i++) { // assume an initial fitness of 0 double fitness = 0; // for each test case for (int t = 0; t < testCases.size(); t++) { - population.getChromosome(i).setInputs((Object[]) testCases.get(t).getInputs()); + population.get(i).setInputs((Object[]) testCases.get(t).getInputs()); // check each output - for (int o = 0; o < resources.outputs(); o++) { - Double cgpValue = (Double) population.getChromosome(i).getOutput(o).calculate(); + for (int o = 0; o < getResources().outputs(); o++) { + Double cgpValue = (Double) population.get(i).getOutput(o).calculate(); Double dataValue = testCases.get(t).getOutput(o); if (hitsBasedFitness.get()) { if (Math.abs(cgpValue - dataValue) <= errorThreshold.get()) { @@ -107,11 +106,8 @@ public class SymbolicRegressionProblem extends TestCaseProblem<Double> { } } // assign the resulting fitness to the respective individual - population.getChromosome(i).setFitness(fitness); + population.get(i).setFitness(fitness); } - - // sort population - population.sortAscending(); } @Override @@ -130,19 +126,27 @@ public class SymbolicRegressionProblem extends TestCaseProblem<Double> { } @Override - public boolean isPerfectSolution(Chromosome fittest) { + public int perfectSolutionFound(Population population) { // higher fitness is better - return fittest.getFitness() >= maxFitness.get() - perfectionThreshold.get(); + for (int i = 0; i < getResources().populationSize(); i++) { + if (population.get(i).getFitness() >= maxFitness.get() - perfectionThreshold.get()) { + return i; + } + } + return -1; } @Override - public boolean isImprovement(Chromosome fittest) { + public int hasImprovement(Population population) { // higher fitness is better - if (fittest.getFitness() > bestFitness.get()) { - bestFitness.set(fittest.getFitness()); - return true; - } else { - return false; + for (int i = 0; i < getResources().populationSize(); i++) { + System.out.println("checking for improvement"); + if (population.get(i).getFitness() > bestFitness.get()) { + System.out.println("found a better chr, " + i); + bestFitness.set(population.get(i).getFitness()); + return i; + } } + return -1; } } diff --git a/src/jcgp/backend/modules/problem/TestCaseProblem.java b/src/jcgp/backend/modules/problem/TestCaseProblem.java index c11fab4..69c078d 100644 --- a/src/jcgp/backend/modules/problem/TestCaseProblem.java +++ b/src/jcgp/backend/modules/problem/TestCaseProblem.java @@ -30,7 +30,7 @@ public abstract class TestCaseProblem<T> extends Problem { * contains arrays of inputs and outputs and associated getters. * * @author Eduardo Pedroni - * @param <U> + * @param <U> the data type of the test case. */ public static class TestCase<U> { private U[] inputs; @@ -80,7 +80,6 @@ public abstract class TestCaseProblem<T> extends Problem { } protected ObservableList<TestCase<T>> testCases; - protected Resources resources; /** * Creates a new TestCaseProblem object. @@ -88,8 +87,7 @@ public abstract class TestCaseProblem<T> extends Problem { * @param resources a reference to the experiment's resources. */ public TestCaseProblem(Resources resources) { - super(); - this.resources = resources; + super(resources); testCases = FXCollections.observableArrayList(); } @@ -139,12 +137,12 @@ public abstract class TestCaseProblem<T> extends Problem { public final void addTestCase(String[] inputs, String[] outputs) { TestCase<T> testCase = parseTestCase(inputs, outputs); - if (testCase.getInputs().length != resources.inputs()) { + if (testCase.getInputs().length != getResources().inputs()) { throw new IllegalArgumentException("Received test case with " + testCase.getInputs().length + - " inputs but need exactly " + resources.inputs()); - } else if (testCase.getOutputs().length != resources.outputs()) { + " inputs but need exactly " + getResources().inputs()); + } else if (testCase.getOutputs().length != getResources().outputs()) { throw new IllegalArgumentException("Received test case with " + testCase.getOutputs().length + - " outputs but need exactly " + resources.outputs()); + " outputs but need exactly " + getResources().outputs()); } else { this.testCases.add(testCase); maxFitness.set(getMaxFitness()); diff --git a/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java b/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java deleted file mode 100644 index 94417ae..0000000 --- a/src/jcgp/backend/modules/problem/TravellingSalesmanProblem.java +++ /dev/null @@ -1,56 +0,0 @@ -package jcgp.backend.modules.problem; - -import java.io.File; - -import jcgp.backend.function.TravellingSalesmanFunctions; -import jcgp.backend.population.Chromosome; -import jcgp.backend.population.Population; -import jcgp.backend.resources.ModifiableResources; -import jcgp.backend.resources.Resources; - -/** - * Travelling salesman problem - * <br><br> - * Using this problem type, travelling salesman tours can be evolved. - * {@code parseData()} must be used to load the desired city - * coordinates in the standard .tsp format. - * - * @see TravellingSalesmanFunctions - * @author Eduardo Pedroni - * - */ -public class TravellingSalesmanProblem extends Problem { - - /** - * Construct a new instance of TravellingSalesmanProblem. - * - * @param resources a reference to the experiment's resources. - */ - public TravellingSalesmanProblem(Resources resources) { - setFunctionSet(new TravellingSalesmanFunctions()); - setName("Travelling salesman"); - setFileExtension(".tsp"); - } - - @Override - public void evaluate(Population population, Resources resources) { - // TODO Auto-generated method stub - } - - @Override - public boolean isPerfectSolution(Chromosome fittest) { - // TODO Auto-generated method stub - return false; - } - - @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 index f0abe50..8c869ac 100644 --- a/src/jcgp/backend/parameters/BooleanParameter.java +++ b/src/jcgp/backend/parameters/BooleanParameter.java @@ -1,6 +1,7 @@ package jcgp.backend.parameters; import javafx.beans.property.SimpleBooleanProperty; +import jcgp.backend.parameters.monitors.BooleanMonitor; /** * Parameter subclass for the boolean type. Most of the diff --git a/src/jcgp/backend/parameters/DoubleParameter.java b/src/jcgp/backend/parameters/DoubleParameter.java index 14b3151..235848f 100644 --- a/src/jcgp/backend/parameters/DoubleParameter.java +++ b/src/jcgp/backend/parameters/DoubleParameter.java @@ -1,6 +1,7 @@ package jcgp.backend.parameters; import javafx.beans.property.SimpleDoubleProperty; +import jcgp.backend.parameters.monitors.DoubleMonitor; /** * Parameter subclass for the double type. Most of the diff --git a/src/jcgp/backend/parameters/IntegerParameter.java b/src/jcgp/backend/parameters/IntegerParameter.java index d0d7328..2c3723a 100644 --- a/src/jcgp/backend/parameters/IntegerParameter.java +++ b/src/jcgp/backend/parameters/IntegerParameter.java @@ -1,6 +1,7 @@ package jcgp.backend.parameters; import javafx.beans.property.SimpleIntegerProperty; +import jcgp.backend.parameters.monitors.IntegerMonitor; /** * Parameter subclass for the double type. Most of the diff --git a/src/jcgp/backend/parameters/Parameter.java b/src/jcgp/backend/parameters/Parameter.java index c04dee9..c605180 100644 --- a/src/jcgp/backend/parameters/Parameter.java +++ b/src/jcgp/backend/parameters/Parameter.java @@ -34,7 +34,7 @@ import javafx.beans.property.ReadOnlyProperty; * will likely have different validity criteria. The type of status is * {@link ParameterStatus}, an enum type defining all valid states. * - * @see Module, ParameterStatus + * @see jcgp.backend.modules.Module * @author Eduardo Pedroni * @param <T> the data type stored in the parameter. */ diff --git a/src/jcgp/backend/parameters/ParameterStatus.java b/src/jcgp/backend/parameters/ParameterStatus.java index 4041cad..558f57f 100644 --- a/src/jcgp/backend/parameters/ParameterStatus.java +++ b/src/jcgp/backend/parameters/ParameterStatus.java @@ -11,6 +11,7 @@ package jcgp.backend.parameters; * <li>WARNING_RESET: the new parameter value is technically valid * but will require a reset.</li> * <li>VALID: the new value is valid.</li> + * </ul> * <br><br> * The above definitions are final in the sense that they outline * how parameters are treated by the program depending on their diff --git a/src/jcgp/backend/parsers/ChromosomeParser.java b/src/jcgp/backend/parsers/ChromosomeParser.java index c35b5b9..f5f25ae 100644 --- a/src/jcgp/backend/parsers/ChromosomeParser.java +++ b/src/jcgp/backend/parsers/ChromosomeParser.java @@ -137,8 +137,9 @@ public abstract class ChromosomeParser { * * @param file the file to write to * @param chromosome the chromosome to save + * @param resources a reference to the experiment's resources. */ - public static void save(File file, Chromosome chromosome, ModifiableResources resources) { + public static void save(File file, Chromosome chromosome, Resources resources) { PrintWriter writer; try { writer = new PrintWriter(file); diff --git a/src/jcgp/backend/population/Chromosome.java b/src/jcgp/backend/population/Chromosome.java index b99b817..0dfa801 100644 --- a/src/jcgp/backend/population/Chromosome.java +++ b/src/jcgp/backend/population/Chromosome.java @@ -159,7 +159,8 @@ public class Chromosome implements Comparable<Chromosome> { * this instance. In practice, this iterates through the * entire chromosome making equivalent connections and * setting functions to the same values as those in the - * specified chromosome. + * specified chromosome. It also sets the fitness of the + * copy to the same value as the original. * <br> * It is assumed that both chromosomes have the same * topology; while this method will still run if that is not @@ -205,6 +206,9 @@ public class Chromosome implements Comparable<Chromosome> { System.out.println("Warning: Connection of subtype " + copyOutput.getClass().toString() + " is not explicitly handled by copy constructor."); } } + + // copy fitness as well + this.fitness = clone.getFitness(); } /** @@ -264,7 +268,6 @@ public class Chromosome implements Comparable<Chromosome> { * number of inputs exactly, an exception is thrown. * * @param values the values the input should take. - * @throws ParameterMismatchException if the wrong number of values is received. */ public void setInputs(Object ... values) { // if the values provided don't match the specified number of inputs, the user should be warned diff --git a/src/jcgp/backend/population/MutableElement.java b/src/jcgp/backend/population/MutableElement.java index 33f3890..5782a99 100644 --- a/src/jcgp/backend/population/MutableElement.java +++ b/src/jcgp/backend/population/MutableElement.java @@ -44,7 +44,7 @@ public interface MutableElement { * <li>not reflexive: a.copyOf(a) returns false;</li> * <li>not transitive: if a.copyOf(b) is true and b.copyOf(c) is true, a.copyOf(c) is * not necessarily true since it is possible that a == c.</li> - * + * </ul> * @param element the mutable element to compare to. * @return true if {@code element} is a copy of this element. */ diff --git a/src/jcgp/backend/population/Population.java b/src/jcgp/backend/population/Population.java index 5d549e9..70cfda8 100644 --- a/src/jcgp/backend/population/Population.java +++ b/src/jcgp/backend/population/Population.java @@ -3,6 +3,7 @@ package jcgp.backend.population; import java.util.Arrays; import java.util.Collections; +import jcgp.backend.modules.problem.BestFitness; import jcgp.backend.resources.Resources; /** @@ -19,16 +20,6 @@ import jcgp.backend.resources.Resources; * experiment's specified seed. If an entirely random population * is needed, {@code reinitialise()} should be used to randomise * all chromosomes without creating a new instance of {@code Population}. - * <br><br> - * By convention the population's chromosomes should always be sorted in - * order of fitness, from worst to best. This cannot be done automatically - * since a higher fitness value does not necessarily mean better fitness; - * some problem types might instead interpret fitness 0 as a perfect solution. - * Sorting the population is done easily using {@code sortAscending()} and - * {@code sortDescending()}, and should be done by the problem type as appropriate. - * It is <b>critical</b> to sort the population after it is evaluated as - * evolutionary strategies will obey the convention and assume the population - * is sorted in order of fitness. * * * @author Eduardo Pedroni @@ -75,7 +66,7 @@ public class Population { * @param index the chromosome to return. * @return the indexed chromosome. */ - public Chromosome getChromosome(int index) { + public Chromosome get(int index) { return chromosomes[index]; } @@ -112,46 +103,19 @@ public class Population { chromosomes[c].reinitialiseConnections(); } } - - /** - * The fittest chromosome, by convention, is the last one - * in the array. Problem evaluation methods are expected to - * sort the population into ascending order of fitness, such - * that the best chromosome is in the last position (size - 1). - * This method assumes that the population is sorted as such - * and returns the last chromosome in the array. - * - * @return the fittest chromosome in the population. - */ - public Chromosome getFittest() { - return chromosomes[chromosomes.length - 1]; - } - - /** - * The fittest chromosome, by convention, is the last one - * in the array. Problem evaluation methods are expected to - * sort the population into ascending order of fitness, such - * that the best chromosome is in the last position (size - 1). - * This method assumes that the population is sorted as such - * and returns the last index in the array. - * - * @return the index of the fittest chromosome. - */ - public int getFittestIndex() { - return chromosomes.length - 1; - } - - /** - * Sort the population into ascending order of fitness. - */ - public void sortAscending() { - Arrays.sort(chromosomes); - } - + /** - * Sort the population into descending order of fitness. + * Sorts the population in ascending order of fitness quality. + * What this means is that the best fitness chromosome will be + * in the last position, even though it might have the lowest + * fitness value. Fitness orientation as specified in the resources + * is respected. */ - public void sortDescending() { - Arrays.sort(chromosomes, Collections.reverseOrder()); + public void sort() { + if (resources.fitnessOrientation() == BestFitness.HIGH) { + Arrays.sort(chromosomes); + } else { + Arrays.sort(chromosomes, Collections.reverseOrder()); + } } } diff --git a/src/jcgp/backend/resources/ModifiableResources.java b/src/jcgp/backend/resources/ModifiableResources.java index 3dab2aa..5df26e2 100644 --- a/src/jcgp/backend/resources/ModifiableResources.java +++ b/src/jcgp/backend/resources/ModifiableResources.java @@ -3,6 +3,7 @@ package jcgp.backend.resources; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import jcgp.backend.function.FunctionSet; +import jcgp.backend.modules.problem.BestFitness; import jcgp.backend.parameters.IntegerParameter; import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.parameters.monitors.IntegerMonitor; @@ -137,6 +138,13 @@ public class ModifiableResources extends Resources { } /** + * @param newOrientation the new orientation to set. + */ + public void setFitnessOrientation(BestFitness newOrientation) { + this.fitnessOrientation = newOrientation; + } + + /** * @return the rows parameter. */ public IntegerParameter getRowsParameter() { diff --git a/src/jcgp/backend/resources/Resources.java b/src/jcgp/backend/resources/Resources.java index ebd6fb2..59a29ea 100644 --- a/src/jcgp/backend/resources/Resources.java +++ b/src/jcgp/backend/resources/Resources.java @@ -4,6 +4,7 @@ import java.util.Random; import jcgp.backend.function.Function; import jcgp.backend.function.FunctionSet; +import jcgp.backend.modules.problem.BestFitness; import jcgp.backend.parameters.IntegerParameter; /** @@ -15,6 +16,8 @@ import jcgp.backend.parameters.IntegerParameter; * 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()} which doesn't exist. + * The fitness orientation of the problem being solved can also be retrieved using {@code fitnessOrientation()}. + * Evolutionary strategies will typically use this to perform selection. * <br><br> * In addition to parameters, this class also offers utility methods. Any necessary random numbers * should be obtained using {@code getRandomInt()} and {@code getRandomDouble()} as these methods @@ -23,7 +26,7 @@ import jcgp.backend.parameters.IntegerParameter; * 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). * - * @see Parameter, Console, FunctionSet + * @see jcgp.backend.parameters.Parameter * @author Eduardo Pedroni * */ @@ -37,6 +40,8 @@ public class Resources { protected Console console; + protected BestFitness fitnessOrientation; + /** * @return the number of rows. */ @@ -135,6 +140,13 @@ public class Resources { return reportInterval.get(); } + /** + * @return the fitness orientation. + */ + public BestFitness fitnessOrientation() { + return fitnessOrientation; + } + /* * Utility functions */ diff --git a/src/jcgp/backend/tests/PopulationTests.java b/src/jcgp/backend/tests/PopulationTests.java index 2f36ce1..21789d7 100644 --- a/src/jcgp/backend/tests/PopulationTests.java +++ b/src/jcgp/backend/tests/PopulationTests.java @@ -46,7 +46,7 @@ public class PopulationTests { int chromosomes = 0; while (true) { try { - population.getChromosome(chromosomes); + population.get(chromosomes); } catch (IndexOutOfBoundsException e) { break; } @@ -64,6 +64,6 @@ public class PopulationTests { // initialise a population with a copy of it population = new Population(oc, resources); // check that the first parent chromosome is identical to, but not the same instance as, the one given - assertTrue("Incorrect chromosome in population.", population.getChromosome(0).compareGenesTo(oc) && population.getChromosome(0) != oc); + assertTrue("Incorrect chromosome in population.", population.get(0).compareGenesTo(oc) && population.get(0) != oc); } } diff --git a/src/jcgp/gui/GUI.java b/src/jcgp/gui/GUI.java index cd4778f..437a739 100644 --- a/src/jcgp/gui/GUI.java +++ b/src/jcgp/gui/GUI.java @@ -257,4 +257,8 @@ public class GUI extends Application { public void flushConsole() { console.flush(); } + + public int getChromosomeIndex() { + return populationPane.getSelectionModel().getSelectedIndex(); + } } diff --git a/src/jcgp/gui/console/GUIConsole.java b/src/jcgp/gui/console/GUIConsole.java index de4b378..b3d037c 100644 --- a/src/jcgp/gui/console/GUIConsole.java +++ b/src/jcgp/gui/console/GUIConsole.java @@ -15,12 +15,25 @@ import javafx.scene.layout.AnchorPane; import jcgp.backend.resources.Console; import jcgp.gui.GUI; +/** + * Console pane used by the GUI to display CGP output messages. + * This class realises {@code Console}. It consists of a JavaFX + * {@code TextArea} and a {@code StringBuffer}. The buffer is filled + * as print messages are queued. Calling {@code flush()} writes the + * contents of the buffer to the {@code TextArea} and empties the buffer. + * + * @see Console + * @author Eduardo Pedroni + * + */ public class GUIConsole extends AnchorPane implements Console { private TextArea textArea = new TextArea("Welcome to JCGP!\n"); - private StringBuffer printBuffer = new StringBuffer(); + /** + * Creates a new instance of this class. + */ public GUIConsole() { super(); textArea.setEditable(false); @@ -31,7 +44,10 @@ public class GUIConsole extends AnchorPane implements Console { * This has not been fixed as of 8/4/2014. * * The following code modifies the EventDispatcher to consume the right mouse - * button click, preventing the default menu from appearing. + * button click, preventing the default menu from appearing. It propagates the mouse + * click further so other elements will respond appropriately. + * + * TODO this should be refactored once the API is updated. */ final EventDispatcher initial = textArea.getEventDispatcher(); textArea.setEventDispatcher(new EventDispatcher() { @@ -76,6 +92,7 @@ public class GUIConsole extends AnchorPane implements Console { new SeparatorMenuItem(), clearConsole)); + // anchor the text area so it resizes automatically AnchorPane.setTopAnchor(textArea, GUI.RESIZE_MARGIN); AnchorPane.setBottomAnchor(textArea, 0.0); AnchorPane.setRightAnchor(textArea, 0.0); diff --git a/src/jcgp/gui/dragresize/HorizontalDragResize.java b/src/jcgp/gui/dragresize/HorizontalDragResize.java index 84c322f..b618b74 100644 --- a/src/jcgp/gui/dragresize/HorizontalDragResize.java +++ b/src/jcgp/gui/dragresize/HorizontalDragResize.java @@ -7,9 +7,11 @@ import javafx.scene.layout.Region; import jcgp.gui.GUI; /** - * - * Decorator pattern. - * + * This class adds horizontal drag resize functionality to any + * arbitrary region provided. This is done by using the static + * method {@code makeDragResizable()}. + * <br><br> + * This is based on a class by Andrew Till found on: * http://andrewtill.blogspot.co.uk/2012/12/dragging-to-resize-javafx-region.html * */ @@ -18,13 +20,29 @@ public class HorizontalDragResize { private boolean dragging = false; private final Region region; + /** + * For internal use only, creates an instance of the actual + * resizer used. + * + * @param region the region to make resizable. + */ private HorizontalDragResize(Region region) { this.region = region; } + /** + * Makes the specified region drag resizable. + * This particular implementation only creates a resize + * click-and-drag area on the left side of the region. + * The resize area is defined by {@code GUI.RESIZE_MARGIN}. + * + * @param region the region to make resizable. + */ public static void makeDragResizable(final Region region) { + // make the instance, this actually performs the resizing final HorizontalDragResize dr = new HorizontalDragResize(region); + // set mouse listeners region.setOnMousePressed(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { @@ -52,12 +70,22 @@ public class HorizontalDragResize { } + /** + * If the press happened in the resize area, raise the drag flag. + * + * @param event the associated mouse event. + */ private void mousePressed(MouseEvent event) { if(isInDraggableZone(event)) { dragging = true; } } + /** + * If drag flag is high, resize the region to match the mouse position. + * + * @param event the associated mouse event. + */ private void mouseDragged(MouseEvent event) { if(dragging) { double newWidth = region.getWidth() - event.getX(); @@ -69,20 +97,33 @@ public class HorizontalDragResize { } } + /** + * Change the cursor if the mouse position overlaps with the resize area. + * + * @param event the associated mouse event. + */ private void mouseMoved(MouseEvent event) { if(isInDraggableZone(event) || dragging) { region.setCursor(Cursor.H_RESIZE); - } - else { + } else { region.setCursor(Cursor.DEFAULT); } } + /** + * Finish resizing. + */ private void mouseReleased() { dragging = false; region.setCursor(Cursor.DEFAULT); } + /** + * Assert whether the mouse cursor is in the draggable area defined by {@code GUI.RESIZE_MARGIN}. + * + * @param event the associated mouse event. + * @return true if the mouse position is in the draggable area. + */ private boolean isInDraggableZone(MouseEvent event) { return event.getX() < (GUI.RESIZE_MARGIN); } diff --git a/src/jcgp/gui/dragresize/VerticalDragResize.java b/src/jcgp/gui/dragresize/VerticalDragResize.java index 9397e5d..06186c6 100644 --- a/src/jcgp/gui/dragresize/VerticalDragResize.java +++ b/src/jcgp/gui/dragresize/VerticalDragResize.java @@ -7,7 +7,11 @@ import javafx.scene.layout.Region; import jcgp.gui.GUI; /** - * + * This class adds vertical drag resize functionality to any + * arbitrary region provided. This is done by using the static + * method {@code makeDragResizable()}. + * <br><br> + * This is based on a class by Andrew Till found on: * http://andrewtill.blogspot.co.uk/2012/12/dragging-to-resize-javafx-region.html * */ @@ -16,13 +20,29 @@ public class VerticalDragResize { private boolean dragging = false; private final Region region; + /** + * For internal use only, creates an instance of the actual + * resizer used. + * + * @param region the region to make resizable. + */ private VerticalDragResize(Region region) { this.region = region; } + /** + * Makes the specified region drag resizable. + * This particular implementation only creates a resize + * click-and-drag area on the top side of the region. + * The resize area is defined by {@code GUI.RESIZE_MARGIN}. + * + * @param region the region to make resizable. + */ public static void makeDragResizable(final Region region) { + // make the instance, this actually performs the resizing final VerticalDragResize dr = new VerticalDragResize(region); + // set mouse listeners region.setOnMousePressed(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { @@ -50,12 +70,22 @@ public class VerticalDragResize { } + /** + * If the press happened in the resize area, raise the drag flag. + * + * @param event the associated mouse event. + */ private void mousePressed(MouseEvent event) { if(isInDraggableZone(event)) { dragging = true; } } + /** + * If drag flag is high, resize the region to match the mouse position. + * + * @param event the associated mouse event. + */ private void mouseDragged(MouseEvent event) { if(dragging) { double newHeight = region.getHeight() - event.getY(); @@ -67,6 +97,11 @@ public class VerticalDragResize { } } + /** + * Change the cursor if the mouse position overlaps with the resize area. + * + * @param event the associated mouse event. + */ private void mouseMoved(MouseEvent event) { if(isInDraggableZone(event) || dragging) { region.setCursor(Cursor.V_RESIZE); @@ -76,11 +111,20 @@ public class VerticalDragResize { } } + /** + * Finish resizing. + */ private void mouseReleased() { dragging = false; region.setCursor(Cursor.DEFAULT); } + /** + * Assert whether the mouse cursor is in the draggable area defined by {@code GUI.RESIZE_MARGIN}. + * + * @param event the associated mouse event. + * @return true if the mouse position is in the draggable area. + */ private boolean isInDraggableZone(MouseEvent event) { return event.getY() < (GUI.RESIZE_MARGIN); } diff --git a/src/jcgp/gui/population/ChromosomePane.java b/src/jcgp/gui/population/ChromosomePane.java index 4a47f34..d40de2e 100644 --- a/src/jcgp/gui/population/ChromosomePane.java +++ b/src/jcgp/gui/population/ChromosomePane.java @@ -25,6 +25,8 @@ public class ChromosomePane extends ScrollPane { private int rows, columns; + private Object[] testInputs; + private boolean target = false; private PopulationPane parent; @@ -111,20 +113,21 @@ public class ChromosomePane extends ScrollPane { target = newValue; } - public void updateGenes() { + public void updateGenes(Chromosome chr) { for (int r = 0; r < rows; r++) { for (int c = 0; c < columns; c++) { + guiNodes[r][c].setNode(chr.getNode(r, c)); guiNodes[r][c].updateLines(); guiNodes[r][c].updateText(); } } for (int i = 0; i < guiOutputs.length; i++) { + guiOutputs[i].setOutput(chr.getOutput(i)); guiOutputs[i].updateLines(); } if (isEvaluating()) { - evaluate(0); + setInputs(testInputs); } - } public void unlockOutputs() { @@ -144,29 +147,27 @@ public class ChromosomePane extends ScrollPane { } public void setInputs(Object[] values) { + testInputs = values; for (int i = 0; i < guiInputs.length; i++) { guiInputs[i].setValue(values[i]); - guiInputs[i].updateText(); } - evaluate(0); + updateValues(); } - public void evaluate(int start) { - if (start >= 0 || start < columns) { - for (int c = start; c < columns; c++) { - for (int r = 0; r < rows; r++) { - guiNodes[r][c].calculate(); - guiNodes[r][c].updateText(); - } - } - for (int o = 0; o < guiOutputs.length; o++) { - guiOutputs[o].calculate(); - guiOutputs[o].updateText(); - } - } - } +// public void evaluate(int start) { +// if (start >= 0 || start < columns) { +// for (int c = 0; c < columns; c++) { +// for (int r = 0; r < rows; r++) { +// guiNodes[r][c].updateText(); +// } +// } +// for (int o = 0; o < guiOutputs.length; o++) { +// guiOutputs[o].updateText(); +// } +// } +// } - public void hideValues() { + public void updateValues() { for (int i = 0; i < guiInputs.length; i++) { guiInputs[i].updateText(); } diff --git a/src/jcgp/gui/population/GUIGene.java b/src/jcgp/gui/population/GUIGene.java index 3ace150..0eea045 100644 --- a/src/jcgp/gui/population/GUIGene.java +++ b/src/jcgp/gui/population/GUIGene.java @@ -39,8 +39,6 @@ public abstract class GUIGene extends Group { protected ChromosomePane parent; protected int locked = 0; - - protected Object value; public GUIGene() { text.setFont(Font.font("Arial", 12)); @@ -92,9 +90,5 @@ public abstract class GUIGene extends Group { public abstract void setConnectionLine(GUIGene gene); - public Object getValue() { - return value; - } - public abstract void updateText(); } diff --git a/src/jcgp/gui/population/GUIInput.java b/src/jcgp/gui/population/GUIInput.java index 05372c4..fd66ab4 100644 --- a/src/jcgp/gui/population/GUIInput.java +++ b/src/jcgp/gui/population/GUIInput.java @@ -219,14 +219,13 @@ public class GUIInput extends GUIGene { } public void setValue(Object newValue) { - value = newValue; input.setValue(newValue); } @Override public void updateText() { if (parent.isEvaluating()) { - text.setText("I: " + input.getIndex() + "\n" + value.toString()); + text.setText("I: " + input.getIndex() + "\n" + input.getValue().toString()); } else { text.setText("I: " + input.getIndex()); } diff --git a/src/jcgp/gui/population/GUINode.java b/src/jcgp/gui/population/GUINode.java index 6dfeaa4..d3ae27f 100644 --- a/src/jcgp/gui/population/GUINode.java +++ b/src/jcgp/gui/population/GUINode.java @@ -398,7 +398,8 @@ public class GUINode extends GUIGene { public void setChangingConnection(Connection newConnection) { node.setConnection(connectionIndex, newConnection); if (parent.isEvaluating()) { - parent.evaluate(node.getColumn()); + parent.updateValues(); +// parent.evaluate(node.getColumn()); } } @@ -452,24 +453,23 @@ public class GUINode extends GUIGene { public void updateText() { if (parent.isEvaluating()) { - text.setText(node.getFunction() + "\n" + value.toString()); + text.setText(node.getFunction() + "\n" + node.getValue().toString()); } else { text.setText(node.getFunction().toString()); } } - - public void calculate() { - value = node.getValue(); - } public void setFunction(Function function) { node.setFunction(function); if (parent.isEvaluating()) { - calculate(); - parent.evaluate(node.getColumn()); +// parent.evaluate(node.getColumn()); + parent.updateValues(); } else { updateText(); } - + } + + public void setNode(Node newNode) { + node = newNode; } } diff --git a/src/jcgp/gui/population/GUIOutput.java b/src/jcgp/gui/population/GUIOutput.java index 29752cd..5a76298 100644 --- a/src/jcgp/gui/population/GUIOutput.java +++ b/src/jcgp/gui/population/GUIOutput.java @@ -267,10 +267,7 @@ public class GUIOutput extends GUIGene { @Override public void setChangingConnection(Connection newConnection) { output.setConnection(0, newConnection); - if (parent.isEvaluating()) { - calculate(); - updateText(); - } + updateText(); } @Override @@ -308,19 +305,19 @@ public class GUIOutput extends GUIGene { setLocked(true); } } - - public void calculate() { - value = output.getSource().getValue(); - } @Override public void updateText() { if (parent.isEvaluating()) { - text.setText("O: " + output.getIndex() + "\n" + value.toString()); + text.setText("O: " + output.getIndex() + "\n" + output.getSource().getValue().toString()); } else { text.setText("O: " + output.getIndex()); } } + public void setOutput(Output newOutput) { + output = newOutput; + } + } diff --git a/src/jcgp/gui/population/PopulationPane.java b/src/jcgp/gui/population/PopulationPane.java index 5fa6067..4b1b7f8 100644 --- a/src/jcgp/gui/population/PopulationPane.java +++ b/src/jcgp/gui/population/PopulationPane.java @@ -27,7 +27,7 @@ public class PopulationPane extends TabPane { Tab tab; ChromosomePane cp; for (int i = 0; i < jcgp.getResources().populationSize(); i++) { - cp = new ChromosomePane(jcgp.getPopulation().getChromosome(i), gui, this); + cp = new ChromosomePane(jcgp.getPopulation().get(i), gui, this); tab = new Tab("Chr " + i); tab.setContent(cp); getTabs().add(tab); @@ -39,7 +39,7 @@ public class PopulationPane extends TabPane { evaluateTestCase(currentTestCase); } for (int i = 0; i < getTabs().size(); i++) { - ((ChromosomePane) getTabs().get(i).getContent()).updateGenes(); + ((ChromosomePane) getTabs().get(i).getContent()).updateGenes(gui.getExperiment().getPopulation().get(i)); } } @@ -73,7 +73,7 @@ public class PopulationPane extends TabPane { public void hideValues() { evaluating = false; for (int i = 0; i < getTabs().size(); i++) { - ((ChromosomePane) getTabs().get(i).getContent()).hideValues(); + ((ChromosomePane) getTabs().get(i).getContent()).updateValues(); } } diff --git a/src/jcgp/gui/settings/SettingsPane.java b/src/jcgp/gui/settings/SettingsPane.java index 802c1f1..2898dc3 100644 --- a/src/jcgp/gui/settings/SettingsPane.java +++ b/src/jcgp/gui/settings/SettingsPane.java @@ -255,7 +255,7 @@ public class SettingsPane extends AnchorPane { } private Button makeTestCaseButton() { - Button b = new Button("Show data1"); + Button b = new Button("Show data"); b.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { @@ -331,7 +331,7 @@ public class SettingsPane extends AnchorPane { fc.getExtensionFilters().add(new ExtensionFilter("All files", "*.*")); File chrFile = fc.showOpenDialog(gui.getStage()); if (chrFile != null) { - gui.getExperiment().loadChromosome(chrFile, 0); + gui.getExperiment().loadChromosome(chrFile, gui.getChromosomeIndex()); gui.reDraw(); } gui.flushConsole(); @@ -346,7 +346,7 @@ public class SettingsPane extends AnchorPane { fc.getExtensionFilters().add(new ExtensionFilter("All files", "*.*")); File chrFile = fc.showSaveDialog(gui.getStage()); if (chrFile != null) { - gui.getExperiment().saveChromosome(chrFile, 0); + gui.getExperiment().saveChromosome(chrFile, gui.getChromosomeIndex()); } gui.flushConsole(); } @@ -474,6 +474,9 @@ public class SettingsPane extends AnchorPane { parameter.applyValue(); } updateArity(); + if (testCaseTable != null) { + testCaseTable.close(); + } } /** diff --git a/src/jcgp/gui/settings/parameters/GUIParameter.java b/src/jcgp/gui/settings/parameters/GUIParameter.java index 3009340..f896fa3 100644 --- a/src/jcgp/gui/settings/parameters/GUIParameter.java +++ b/src/jcgp/gui/settings/parameters/GUIParameter.java @@ -23,7 +23,7 @@ import jcgp.gui.settings.SettingsPane; * This is the base class for all @code{GUIParameter}s. Using the factory method @code{GUIParameter.create()} * generates an appropriate instance of this class for the specified parameter. * <br><br> - * @code{GUIParameter} is an @code{HBox} containing a @code{Text} for the parameter name + * A @code{GUIParameter} is an @code{HBox} containing a @code{Text} for the parameter name * and a @code{Control} for interaction. * It stores an instance of its associated @code{Parameter} object and also contains a @code{Tooltip} for * displaying status information. diff --git a/src/jcgp/gui/settings/testcase/TestCaseTable.java b/src/jcgp/gui/settings/testcase/TestCaseTable.java index d4c1ff9..d4f789c 100644 --- a/src/jcgp/gui/settings/testcase/TestCaseTable.java +++ b/src/jcgp/gui/settings/testcase/TestCaseTable.java @@ -20,7 +20,10 @@ import jcgp.backend.resources.Resources; import jcgp.gui.GUI; /** - * + * This is a test case table. For problems that have test cases, + * this table shows the test case inputs and outputs. Clicking on + * a test case (one is shown per row) applies the values to all + * chromosome inputs shows the calculated values throughout the chromosome. * * @author Eduardo Pedroni * @@ -29,17 +32,27 @@ public class TestCaseTable extends Stage { private TableView<TestCase<Object>> table; + /** + * Make a new instance of {@code TestCaseTable}. + * + * @param testCaseProblem the {@code TestCaseProblem} whose data must be displayed. + * @param gui a reference to the GUI. + */ public TestCaseTable(final TestCaseProblem<Object> testCaseProblem, final GUI gui) { super(); Resources resources = gui.getExperiment().getResources(); + // create the actual table view table = new TableView<TestCase<Object>>(); + // get test cases from problem ObservableList<TestCase<Object>> testCaseList = testCaseProblem.getTestCases(); + // prepare input and output columns ArrayList<TableColumn<TestCase<Object>, String>> inputs = new ArrayList<TableColumn<TestCase<Object>, String>>(resources.inputs()); ArrayList<TableColumn<TestCase<Object>, String>> outputs = new ArrayList<TableColumn<TestCase<Object>, String>>(resources.outputs()); + // create input columns TableColumn<TestCase<Object>, String> tc; for (int i = 0; i < resources.inputs(); i++) { tc = new TableColumn<TestCase<Object>, String>("I: " + i); @@ -48,13 +61,16 @@ public class TestCaseTable extends Stage { tc.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<TestCase<Object>,String>, ObservableValue<String>>() { @Override public ObservableValue<String> call(CellDataFeatures<TestCase<Object>, String> param) { + // create a new string property and give it the test case value, no need for dynamic binding - this wont change often return new SimpleStringProperty(param.getValue().getInput(index).toString()); } }); tc.setSortable(false); + // set column width so all columns are distributed across the width of the stage tc.prefWidthProperty().bind(table.widthProperty().divide(resources.inputs() + resources.outputs())); } + // create output columns for (int o = 0; o < resources.outputs(); o++) { tc = new TableColumn<TestCase<Object>, String>("O: " + o); outputs.add(tc); @@ -62,37 +78,46 @@ public class TestCaseTable extends Stage { tc.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<TestCase<Object>,String>, ObservableValue<String>>() { @Override public ObservableValue<String> call(CellDataFeatures<TestCase<Object>, String> param) { + // create a new string property and give it the test case value, no need for dynamic binding - this wont change often return new SimpleStringProperty(param.getValue().getOutput(index).toString()); } }); tc.setSortable(false); + // set column width so all columns are distributed across the width of the stage tc.prefWidthProperty().bind(table.widthProperty().divide(resources.inputs() + resources.outputs())); } + // add created columns table.getColumns().addAll(inputs); table.getColumns().addAll(outputs); + // populate table with actual data table.setItems(testCaseList); + // apply test case values when a new test case is selected table.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TestCase<Object>>() { @Override - public void changed( - ObservableValue<? extends TestCase<Object>> observable, TestCase<Object> oldValue, TestCase<Object> newValue) { + public void changed(ObservableValue<? extends TestCase<Object>> observable, TestCase<Object> oldValue, TestCase<Object> newValue) { gui.evaluateTestCase(newValue); } }); + // when the stage is closed, clear the selection + // this doesn't work if the stage is closed by the program for some reason... setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { gui.hideGeneValues(); - table.getSelectionModel().select(null); + table.getSelectionModel().clearSelection(); } }); setScene(new Scene(table)); } + /** + * @return a reference to the actual table of test cases. + */ public TableView<TestCase<Object>> getTable() { return table; } |