diff options
author | Eduardo Pedroni <ep625@york.ac.uk> | 2014-03-23 18:05:13 +0000 |
---|---|---|
committer | Eduardo Pedroni <ep625@york.ac.uk> | 2014-03-23 18:05:13 +0000 |
commit | 0c288cc1952809294c8d70d86b9f41b04878ac2e (patch) | |
tree | ef9671b711fe665a3156594663c083595861a4e6 | |
parent | d3527a63e12c0e5288f1e7d2e2dc18e61d16b760 (diff) |
Majorly refactored, node grid is fully implemented. About to attempt active path locking.
44 files changed, 1986 insertions, 694 deletions
@@ -171,4 +171,22 @@ Adding GUI package, refactoring CGP class to interact appropriately with GUI. 15/3 Currently refactoring Parameters. It is now a part of CGP.class as stated in the phase report. The rest of the program will now be refactored to accommodate these changes. Inversion of -will be employed to avoid static accesses to CGP, and the tests will be modified accordingly. This will allow multiple instances of CGP to co-exist.
\ No newline at end of file +will be employed to avoid static accesses to CGP, and the tests will be modified accordingly. This will allow multiple instances of CGP to co-exist. + +18/3 + +The current state of parameters is not yet perfect. One more refactoring will be carried out to speed up GUI updates. +Modules will be required to return a hashmap of parameters so the GUI can be re-built easily whenever modules change. +Modules should instantiate their parameters in their constructor and use direct referencing internally for extra speed. +The hashmap may be pre-built to save time when getParameters() is called, though this is not critical. + +19/3 + +Refactoring is complete, population tests have been refactored as well. It is worth noting that the new resources +framework leads to a lot of confusing casting. Utility methods will be written to hide away the casting in the CGP +class and simplify the programmer's life. +Registering parameters is no longer necessary. The CGP class will query modules for their parameters when necessary. + + +21/3 +FunctionSet has been rewritten to allow only certain functions to be deactivated, the GUI will now be integrated.
\ No newline at end of file diff --git a/src/jcgp/CGP.java b/src/jcgp/CGP.java index 16d2be7..6b68fd8 100644 --- a/src/jcgp/CGP.java +++ b/src/jcgp/CGP.java @@ -1,153 +1,234 @@ package jcgp; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import java.util.Random; import javafx.beans.property.Property; -import javafx.beans.property.SimpleIntegerProperty; +import jcgp.function.Arithmetic; +import jcgp.function.BitwiseLogic; +import jcgp.function.BooleanLogic; +import jcgp.function.Function; +import jcgp.function.FunctionSet; import jcgp.modules.ea.EvolutionaryAlgorithm; import jcgp.modules.ea.StandardEA; import jcgp.modules.fitness.FitnessFunction; import jcgp.modules.fitness.TestCase; -import jcgp.modules.fitness.TruthTableEvaluator; -import jcgp.modules.function.Arithmetic; -import jcgp.modules.function.BitwiseLogic; -import jcgp.modules.function.BooleanLogic; -import jcgp.modules.function.FunctionSet; +import jcgp.modules.fitness.TestCaseEvaluator; import jcgp.modules.mutator.Mutator; import jcgp.modules.mutator.PointMutator; import jcgp.parameters.BooleanParameter; -import jcgp.parameters.DoubleParameter; import jcgp.parameters.IntegerParameter; import jcgp.parameters.Parameter; import jcgp.population.Population; public class CGP { - // CGP components + /** + * + * + * @author Eduardo Pedroni + * + */ + public static class Resources { + private HashMap<String, Parameter> moduleParameters = new HashMap<String, Parameter>(); + private HashMap<String, Parameter> coreParameters = new HashMap<String, Parameter>(); + private HashMap<String, Parameter> allParameters = new HashMap<String, Parameter>(); + + private Random numberGenerator; + + // function sets + private FunctionSet[] functionSets = new FunctionSet[] { + new Arithmetic(), + new BitwiseLogic(), + new BooleanLogic() }; + private FunctionSet functionSet = functionSets[0]; + + private TestCase[] testCases; + + public Resources() { + createCoreParameters(); + + numberGenerator = new Random((int) get("seed")); + functionSet = functionSets[0]; + + set("arity", functionSet.getMaxArity()); + } + + public Object get(String key) { + return allParameters.get(key).getValue(); + } + + public void set(String key, Object value) { + allParameters.get(key).setValue(value); + } + + public Property<?> getProperty(String key) { + return allParameters.get(key).valueProperty(); + } + + public void setManagedParameter(String key, boolean value) { + allParameters.get(key).setManaged(value); + } + + public void setHiddenParameter(String key, boolean value) { + allParameters.get(key).setHidden(value); + } + + public boolean contains(String key) { + return allParameters.containsKey(key); + } + + private void createCoreParameters() { + coreParameters.put("rows", new IntegerParameter(9, "Rows")); + coreParameters.put("columns", new IntegerParameter(10, "Columns")); + coreParameters.put("inputs", new IntegerParameter(3, "Inputs")); + coreParameters.put("outputs", new IntegerParameter(3, "Outputs")); + coreParameters.put("popSize", new IntegerParameter(5, "Population")); + coreParameters.put("levelsBack", new IntegerParameter(2, "Levels back")); + + coreParameters.put("nodes", new IntegerParameter(90, "Nodes", true, true)); + + coreParameters.put("generations", new IntegerParameter(1000000, "Generations")); + coreParameters.put("currentGen", new IntegerParameter(0, "Generation")); + coreParameters.put("runs", new IntegerParameter(5, "Runs")); + + coreParameters.put("arity", new IntegerParameter(0, "Max arity", true, true)); + + coreParameters.put("seed", new IntegerParameter(123, "Random seed", true, true)); + + coreParameters.put("debug", new BooleanParameter(false, "Debug")); + + allParameters.putAll(coreParameters); + } + + private void resetParameters(EvolutionaryAlgorithm ea, Mutator mu, FitnessFunction ff) { + Iterator<Entry<String, Parameter>> it = coreParameters.entrySet().iterator(); + while (it.hasNext()) { + ((Parameter) ((Map.Entry<String, Parameter>) it.next())).reset(); + } + + allParameters.clear(); + allParameters.putAll(coreParameters); + addModuleParameters(ea, mu, ff); + } + + private void addModuleParameters(EvolutionaryAlgorithm ea, Mutator mu, FitnessFunction ff) { + moduleParameters.clear(); + moduleParameters.putAll(ea.activate(this)); + moduleParameters.putAll(mu.activate(this)); + moduleParameters.putAll(ff.activate(this)); + + allParameters.putAll(moduleParameters); + } + + /* + * Utility functions + */ + public int getRandomInt(int limit) { + return numberGenerator.nextInt(limit); + } + + public double getRandomDouble(int limit) { + return numberGenerator.nextDouble() * limit; + } + + public double getRandomDouble() { + return numberGenerator.nextDouble(); + } + + /* + * Function set functions + */ + public Function getRandomFunction() { + return functionSet.getFunction(numberGenerator.nextInt(functionSet.getFunctionCount())); + } + + public Function getFunction(int index) { + return functionSet.getFunction(index); + } + + public void setFunctionSet(int index) { + functionSet = functionSets[index]; + } + + /* + * Test cases + */ + public void setTestCases(TestCase ... testCases) { + this.testCases = testCases; + } + + public TestCase getTestCase(int index) { + return testCases[index]; + } + + public int getTestCaseCount() { + return testCases.length; + } + } + + private Resources resources = new Resources(); + + /* + * 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. + * + * 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 PointMutator()}; + new PointMutator() }; private Mutator mutator = mutators[0]; + // evolutionary algorithms private EvolutionaryAlgorithm[] evolutionaryAlgorithms = new EvolutionaryAlgorithm[] { - new StandardEA()}; + new StandardEA() }; private EvolutionaryAlgorithm evolutionaryAlgorithm = evolutionaryAlgorithms[0]; + // fitness evaluators private FitnessFunction[] fitnessFunctions = new FitnessFunction[] { - new TruthTableEvaluator()}; + new TestCaseEvaluator() }; private FitnessFunction fitnessFunction = fitnessFunctions[0]; - private FunctionSet[] functionSets = new FunctionSet[] { - new FunctionSet("Arithmetic", - new Arithmetic.Addition(), - new Arithmetic.Subtraction(), - new Arithmetic.Multiplication(), - new Arithmetic.Division()), - - new FunctionSet("32-bit logic", - new BitwiseLogic.And(), - new BitwiseLogic.Or(), - new BitwiseLogic.Nand(), - new BitwiseLogic.Nor(), - new BitwiseLogic.Xor(), - new BitwiseLogic.Xnor(), - new BitwiseLogic.Not()), - - new FunctionSet("1-bit logic", - new BooleanLogic.And(), - new BooleanLogic.Or(), - new BooleanLogic.Nand(), - new BooleanLogic.Nor(), - new BooleanLogic.Xor(), - new BooleanLogic.Xnor(), - new BooleanLogic.Not())}; + // the population of chromosomes + private Population population = new Population(resources); - - - private Population population; - - private HashMap<String, Parameter> parameters = new HashMap<String, Parameter>(); public CGP() { - createBaseParameters(); - - for (int i = 0; i < (int) get("generations"); i++) { - - set("currentGen", ((int) get("currentGen")) + 1); - fitnessFunction.evaluate(population); - evolutionaryAlgorithm.evolve(population, mutator); - - if (evolutionaryAlgorithm.getFittestChromosome().getFitness() >= 3) { - break; - } - } - } - - private void createBaseParameters() { - // make fundamental parameters - parameters.put("rows", new IntegerParameter(3, "Rows")); - parameters.put("columns", new IntegerParameter(3, "Columns")); - parameters.put("inputs", new IntegerParameter(3, "Inputs")); - parameters.put("outputs", new IntegerParameter(3, "Outputs")); - parameters.put("popSize", new IntegerParameter(5, "Population")); + resources.addModuleParameters(evolutionaryAlgorithm, mutator, fitnessFunction); - parameters.put("nodes", new IntegerParameter(9, "", true, true)); + resources.setTestCases(new TestCase(new Integer[]{1, 2, 3}, new Integer[]{4, 5, 6}), + new TestCase(new Integer[]{1, 12, 4}, new Integer[]{6, 21, 2})); - parameters.put("gens", new IntegerParameter(100, "Generations")); - parameters.put("currentGen", new IntegerParameter(0, "Generation")); - parameters.put("runs", new IntegerParameter(5, "Runs")); - - parameters.put("debug", new BooleanParameter(false, "Debug")); +// for (int i = 0; i < (int) resources.get("generations"); i++) { +// +// resources.set("currentGen", ((int) resources.get("currentGen")) + 1); +// +// fitnessFunction.evaluate(population, resources); +// evolutionaryAlgorithm.evolve(population, mutator, resources); +// +// System.out.println("fitness: " + evolutionaryAlgorithm.getFittestChromosome().getFitness()); +// +// if (evolutionaryAlgorithm.getFittestChromosome().getFitness() >= 6) { +// System.out.println("solution found"); +// evolutionaryAlgorithm.getFittestChromosome().printNodes(); +// break; +// } +// } } -// /** -// * -// */ -// private void loadModules() { -// // initialise function set -// FunctionSet functionSet = new FunctionSet(new Arithmetic.Addition(), new Arithmetic.Subtraction(), new Arithmetic.Multiplication()); -// -// // initialise utilities -// Utilities.setResources(new Random(1234), functionSet); -// -// // initialise fitness function and truth table -// TruthTable.setTestCases(new TestCase(new Object[] {2, 5, 4}, new Object[] {1, 10, 15})); -// fitnessFunction = new TruthTableEvaluator(); -// -// // initialise EA -// evolutionaryAlgorithm = new StandardEA(); -// mutator = new StandardMutator(); -// -// // initialise population -// population = new Population(); -// } - - // Parameter methods - public void register(String key, Parameter value) { - parameters.put(key, value); - } - - /** - * @param key - * @return - */ - public Object get(String key) { - return parameters.get(key).getValue(); - } - - public void set(String key, Object value) { - parameters.get(key).setValue(value); - } - - @SuppressWarnings("rawtypes") - public Property getProperty(String key) { - return parameters.get(key).valueProperty(); + + public Resources getResources() { + return resources; } - - public boolean contains(String key) { - return parameters.containsKey(key); + + + public Population getPopulation() { + return population; } - // Utility methods - } diff --git a/src/jcgp/GUI.java b/src/jcgp/GUI.java new file mode 100644 index 0000000..1e1d4e4 --- /dev/null +++ b/src/jcgp/GUI.java @@ -0,0 +1,111 @@ +package jcgp; + +import javafx.application.Application; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TabPane.TabClosingPolicy; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import jcgp.CGP.Resources; +import jcgp.gui.Console; +import jcgp.gui.SettingsPane; +import jcgp.gui.population.ChromosomePane; +import jcgp.gui.population.GUIGene; +import jcgp.gui.population.GUINode; +import jcgp.gui.population.GUIOutput; + +public class GUI extends Application { + + public static final String NEUTRAL_COLOUR = "#FFFFFF"; + public static final String HARD_HIGHLIGHT_COLOUR = "#89AAD6"; + public static final String SOFT_HIGHLIGHT_COLOUR = "#C7DFFF"; + public static final String GOOD_SELECTION_COLOUR = "#BDFFC2"; + public static final String NEUTRAL_SELECTION_COLOUR = "#FBFFB8"; + public static final String BAD_SELECTION_COLOUR = "#FF9C9C"; + + private static CGP cgp; + public static Resources resources; + + + private BorderPane window; + + private ChromosomePane[] chromosomes; + private TabPane mainPane; + + private static Console console; + private SettingsPane settings; + + public static void main(String[] args) { + + cgp = new CGP(); + resources = cgp.getResources(); + + launch(); + + } + + @Override + public void start(Stage primaryStage) throws Exception { + + /* + * Instantiate the various GUI elements here. + * + * + */ + + mainPane = new TabPane(); + mainPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE); + chromosomes = new ChromosomePane[(int) cgp.getResources().get("popSize")]; + Tab tab; + for (int i = 0; i < chromosomes.length; i++) { + chromosomes[i] = new ChromosomePane(cgp.getPopulation().getChromosome(i), cgp.getResources()); + tab = new Tab("Chr " + i); + tab.setContent(chromosomes[i]); + mainPane.getTabs().add(tab); + } + + mainPane.setPrefHeight(500); + + window = new BorderPane(); + window.setCenter(mainPane); + + Scene scene = new Scene(window); + + primaryStage.addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // preemptively determine whether this event will reach a GUIGene + // sodding Controls... + if (event.getTarget() instanceof Node) { + Node source = ((Node) event.getTarget()); + while (source != null) { + if (source instanceof GUIGene) { + return; + } + source = source.getParent(); + } + } + event.consume(); + ((GUIGene) event.getGestureSource()).resetState(); + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).updateLines(); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).updateLine(); + } + } + }); + + primaryStage.setMinHeight(600); + primaryStage.setMinWidth(800); + + primaryStage.setScene(scene); + primaryStage.show(); + + + } + +} diff --git a/src/jcgp/Main.java b/src/jcgp/Main.java deleted file mode 100644 index d2b438d..0000000 --- a/src/jcgp/Main.java +++ /dev/null @@ -1,9 +0,0 @@ -package jcgp; - -public class Main { - - public static void main(String[] args) { - CGP cgp = new CGP(); - - } -} diff --git a/src/jcgp/TruthTable.java b/src/jcgp/TruthTable.java deleted file mode 100644 index e62f56b..0000000 --- a/src/jcgp/TruthTable.java +++ /dev/null @@ -1,21 +0,0 @@ -package jcgp; - -import jcgp.modules.fitness.TestCase; - -public class TruthTable { - - private static TestCase[] testCases; - - public static void setTestCases(TestCase ... testCases) { - TruthTable.testCases = testCases; - } - - public static TestCase getTestCase(int index) { - return testCases[index]; - } - - public static int getTestCaseCount() { - return testCases.length; - } - -} diff --git a/src/jcgp/Utilities.java b/src/jcgp/Utilities.java deleted file mode 100644 index ff5387f..0000000 --- a/src/jcgp/Utilities.java +++ /dev/null @@ -1,98 +0,0 @@ -package jcgp; - -import java.util.Random; - -import jcgp.modules.function.Function; -import jcgp.modules.function.FunctionSet; -import jcgp.parameters.Parameters; -import jcgp.population.*; - -public class Utilities { - - private static Random numberGenerator; - private static FunctionSet functionSet; - - public static void setResources(Random numberGenerator, FunctionSet functionSet) { - Utilities.numberGenerator = numberGenerator; - Utilities.functionSet = functionSet; - } - - public static int getRandomInt(int limit){ - return numberGenerator.nextInt(limit); - } - - public static double getRandomDouble(int limit){ - return numberGenerator.nextDouble() * limit; - } - - /** - * @param chromosome the chromosome to choose from - * @return a random input - */ - public static Input getRandomInput(Chromosome chromosome){ - return chromosome.getInput(getRandomInt(Parameters.getInputs())); - } - - /** - * Returns a random allowed node respecting levels back. - * - * This method will NOT pick inputs. - * - * @param chromosome the chromosome to pick from - * @param column the column to use as reference - * @return a random node - */ - public static Node getRandomNode(Chromosome chromosome, int column){ - // work out the allowed range obeying levels back - int allowedColumns = ((column >= Parameters.getLevelsBack()) ? Parameters.getLevelsBack() : column); - int offset = column - allowedColumns; - - // pick a random allowed column and row - int randomColumn = (getRandomInt(allowedColumns) + offset); - int randomRow = (getRandomInt(Parameters.getRows())); - - return chromosome.getNode(randomRow, randomColumn); - } - - /** - * Returns a random allowed node. - * - * This method will NOT pick inputs. - * - * @param chromosome the chromosome to pick from - * @param column the column to use as reference - * @param levelsBack whether or not to respect levels back - * @return a random node - */ - public static Node getRandomNode(Chromosome chromosome, int column, boolean levelsBack){ - if (levelsBack) { - return getRandomNode(chromosome, column); - } else { - // pick any random column before the given column - int randomColumn = (getRandomInt(column)); - // pick a random rowgetColumns - int randomRow = (getRandomInt(Parameters.getRows())); - return chromosome.getNode(randomRow, randomColumn); - } - } - - - /** - * pick from any column - use this for setting outputs - * - * @param chromosome - * @return - */ - public static Node getRandomNode(Chromosome chromosome) { - return chromosome.getNode(getRandomInt(Parameters.getRows()), getRandomInt(Parameters.getColumns())); - } - - public static Function getRandomFunction() { - return functionSet.getFunction(Utilities.getRandomInt(functionSet.getFunctionCount())); - } - - public static Function getFunction(int index) { - return functionSet.getFunction(index); - } - -} diff --git a/src/jcgp/modules/function/Arithmetic.java b/src/jcgp/function/Arithmetic.java index aa5e9bf..7ec1366 100644 --- a/src/jcgp/modules/function/Arithmetic.java +++ b/src/jcgp/function/Arithmetic.java @@ -1,10 +1,25 @@ -package jcgp.modules.function; +package jcgp.function; + +import java.util.ArrayList; +import java.util.Arrays; import jcgp.exceptions.InvalidArgumentsException; import jcgp.population.Connection; -public class Arithmetic { - +public class Arithmetic extends FunctionSet { + + public Arithmetic() { + maxArity = 2; + name = "Arithmetic"; + functionList = new Function[]{ + new Addition(), + new Subtraction(), + new Multiplication(), + new Division()}; + + allowedFunctions = new ArrayList<Function>(Arrays.asList(functionList)); + } + public static class Addition extends Function { private int arity = 2; @@ -26,6 +41,11 @@ public class Arithmetic { public int getArity() { return arity; } + + @Override + public String getName() { + return "Addition"; + } } public static class Subtraction extends Function { @@ -49,6 +69,11 @@ public class Arithmetic { public int getArity() { return arity; } + + @Override + public String getName() { + return "Subtraction"; + } } public static class Multiplication extends Function { @@ -72,6 +97,11 @@ public class Arithmetic { public int getArity() { return arity; } + + @Override + public String getName() { + return "Multiplication"; + } } public static class Division extends Function { @@ -100,6 +130,11 @@ public class Arithmetic { public int getArity() { return arity; } + + @Override + public String getName() { + return "Division"; + } } } diff --git a/src/jcgp/modules/function/BitwiseLogic.java b/src/jcgp/function/BitwiseLogic.java index c8452e6..033534d 100644 --- a/src/jcgp/modules/function/BitwiseLogic.java +++ b/src/jcgp/function/BitwiseLogic.java @@ -1,9 +1,27 @@ -package jcgp.modules.function; +package jcgp.function; + +import java.util.ArrayList; +import java.util.Arrays; import jcgp.exceptions.InvalidArgumentsException; import jcgp.population.Connection; -public class BitwiseLogic { +public class BitwiseLogic extends FunctionSet { + + public BitwiseLogic() { + maxArity = 2; + name = "32-bit Logic"; + functionList = new Function[]{ + new And(), + new Or(), + new Not(), + new Xor(), + new Nand(), + new Nor(), + new Xnor()}; + + allowedFunctions = new ArrayList<Function>(Arrays.asList(functionList)); + } public static class And extends Function { private int arity = 2; @@ -25,6 +43,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "AND"; + } } public static class Or extends Function { @@ -47,6 +70,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "OR"; + } } public static class Not extends Function { @@ -68,6 +96,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "NOT"; + } } public static class Xor extends Function { @@ -90,6 +123,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "XOR"; + } } public static class Nand extends Function { @@ -112,6 +150,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "NAND"; + } } public static class Nor extends Function { @@ -134,6 +177,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "NOR"; + } } public static class Xnor extends Function { @@ -156,6 +204,11 @@ public class BitwiseLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "XNOR"; + } } diff --git a/src/jcgp/modules/function/BooleanLogic.java b/src/jcgp/function/BooleanLogic.java index f98d1db..d31b798 100644 --- a/src/jcgp/modules/function/BooleanLogic.java +++ b/src/jcgp/function/BooleanLogic.java @@ -1,9 +1,27 @@ -package jcgp.modules.function; +package jcgp.function; + +import java.util.ArrayList; +import java.util.Arrays; import jcgp.exceptions.InvalidArgumentsException; import jcgp.population.Connection; -public class BooleanLogic { +public class BooleanLogic extends FunctionSet { + + public BooleanLogic() { + maxArity = 2; + name = "1-bit Logic"; + functionList = new Function[]{ + new And(), + new Or(), + new Not(), + new Xor(), + new Nand(), + new Nor(), + new Xnor()}; + + allowedFunctions = new ArrayList<Function>(Arrays.asList(functionList)); + } public static class And extends Function { private int arity = 2; @@ -25,6 +43,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "AND"; + } } public static class Or extends Function { @@ -47,6 +70,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "OR"; + } } public static class Not extends Function { @@ -68,6 +96,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "NOT"; + } } public static class Xor extends Function { @@ -90,6 +123,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "XOR"; + } } public static class Nand extends Function { @@ -112,6 +150,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "NAND"; + } } public static class Nor extends Function { @@ -134,6 +177,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "NOR"; + } } public static class Xnor extends Function { @@ -156,6 +204,11 @@ public class BooleanLogic { public int getArity() { return arity; } + + @Override + public String getName() { + return "XNOR"; + } } diff --git a/src/jcgp/modules/function/Function.java b/src/jcgp/function/Function.java index 3314c2f..64dd206 100644 --- a/src/jcgp/modules/function/Function.java +++ b/src/jcgp/function/Function.java @@ -1,12 +1,13 @@ -package jcgp.modules.function; +package jcgp.function; import jcgp.exceptions.InvalidArgumentsException; import jcgp.population.Connection; public abstract class Function { - + public abstract Object run(Connection ... connections) throws InvalidArgumentsException; public abstract int getArity(); + public abstract String getName(); } diff --git a/src/jcgp/function/FunctionSet.java b/src/jcgp/function/FunctionSet.java new file mode 100644 index 0000000..07b4ea4 --- /dev/null +++ b/src/jcgp/function/FunctionSet.java @@ -0,0 +1,54 @@ +package jcgp.function; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * + * @author Eduardo Pedroni + * + */ +public abstract class FunctionSet { + protected Function[] functionList; + protected ArrayList<Function> allowedFunctions; + protected int maxArity; + protected String name; + +// public int getTotalFunctionCount() { +// return functionList.length; +// } + + public int getFunctionCount() { + return allowedFunctions.size(); + } + + public Function getFunction(int index) { + return allowedFunctions.get(index); + } + + public int getMaxArity(){ + return maxArity; + } + + public String getName() { + return name; + } + + public void disableFunction(int index) { + for (Iterator<Function> iterator = allowedFunctions.iterator(); iterator.hasNext();) { + if (index < functionList.length) { + Function function = (Function) iterator.next(); + if (function == functionList[index]) { + iterator.remove(); + } + } + + } + } + + public void enableFunction(int index) { + if (!allowedFunctions.contains(functionList[index])) { + allowedFunctions.add(functionList[index]); + } + } + }
\ No newline at end of file diff --git a/src/jcgp/gui/Console.java b/src/jcgp/gui/Console.java new file mode 100644 index 0000000..9008531 --- /dev/null +++ b/src/jcgp/gui/Console.java @@ -0,0 +1,13 @@ +package jcgp.gui; + +import javafx.scene.control.TextArea; + +public class Console extends TextArea { + + public Console() { + super(); + setDisable(true); + setPrefHeight(100); + } + +} diff --git a/src/jcgp/gui/GUI.java b/src/jcgp/gui/GUI.java deleted file mode 100644 index 0e4b91a..0000000 --- a/src/jcgp/gui/GUI.java +++ /dev/null @@ -1,14 +0,0 @@ -package jcgp.gui; - -import javafx.application.Application; -import javafx.stage.Stage; - -public class GUI extends Application { - - @Override - public void start(Stage primaryStage) throws Exception { - - - } - -} diff --git a/src/jcgp/gui/SettingsPane.java b/src/jcgp/gui/SettingsPane.java new file mode 100644 index 0000000..53056d3 --- /dev/null +++ b/src/jcgp/gui/SettingsPane.java @@ -0,0 +1,11 @@ +package jcgp.gui; + +import javafx.scene.layout.VBox; + +public class SettingsPane extends VBox { + + public SettingsPane() { + super(); + setPrefSize(180, 500); + } +} diff --git a/src/jcgp/gui/population/ChromosomePane.java b/src/jcgp/gui/population/ChromosomePane.java new file mode 100644 index 0000000..546314e --- /dev/null +++ b/src/jcgp/gui/population/ChromosomePane.java @@ -0,0 +1,97 @@ +package jcgp.gui.population; + +import java.util.ArrayList; + +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Pane; +import javafx.scene.shape.Line; +import jcgp.CGP.Resources; +import jcgp.population.Chromosome; + + +public class ChromosomePane extends ScrollPane { + + private GUINode[][] guiNodes; + private GUIInput[] guiInputs; + private GUIOutput[] guiOutputs; + + private Pane content; + + private ArrayList<Line> connectionLines; + + public ChromosomePane(Chromosome chromosome, Resources resources) { + super(); + connectionLines = new ArrayList<Line>(); + + content = new Pane(); + content.setId("content pane for genes"); + + // generate the GUIGenes + // inputs + guiInputs = new GUIInput[(int) resources.get("inputs")]; + for (int i = 0; i < guiInputs.length; i++) { + // make the GUI elements + guiInputs[i] = new GUIInput(this, chromosome.getInput(i)); + content.getChildren().addAll(guiInputs[i]); + } + // nodes + guiNodes = new GUINode[(int) resources.get("rows")][(int) resources.get("columns")]; + double angle, xPos, yPos; + for (int r = 0; r < guiNodes.length; r++) { + for (int c = 0; c < guiNodes[r].length; c++) { + // make the connection lines + Line lines[] = new Line[(int) resources.get("arity")]; + for (int l = 0; l < lines.length; l++) { + angle = ((((double) (l + 1)) / ((double) (lines.length + 1))) * GUIGene.THETA) - (GUIGene.THETA / 2); + xPos = (-Math.cos(angle) * GUIGene.NODE_RADIUS) + (((c + 1) * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS); + yPos = (Math.sin(angle) * GUIGene.NODE_RADIUS) + ((r * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS); + + lines[l] = new Line(xPos, yPos, xPos, yPos); + lines[l].setMouseTransparent(true); + lines[l].setVisible(false); + connectionLines.add(lines[l]); + } + // make the GUI elements + guiNodes[r][c] = new GUINode(this, chromosome.getNode(r, c), resources, lines); + } + content.getChildren().addAll(guiNodes[r]); + } + + // outputs + guiOutputs = new GUIOutput[(int) resources.get("outputs")]; + for (int i = 0; i < guiOutputs.length; i++) { + xPos = (((int) resources.get("columns") + 1) * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)); + yPos = (chromosome.getOutput(i).getIndex() * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS; + // make the line + Line line = new Line(xPos, yPos, xPos, yPos); + line.setMouseTransparent(true); + line.setVisible(false); + connectionLines.add(line); + // make the GUI elements + guiOutputs[i] = new GUIOutput(this, chromosome.getOutput(i), resources, line); + content.getChildren().addAll(guiOutputs[i]); + } + + content.getChildren().addAll(connectionLines); + setPrefWidth(620); + setContent(content); + } + + + public GUINode getGuiNode(int row, int column) { + return guiNodes[row][column]; + } + + public GUIInput getGuiInput(int index) { + return guiInputs[index]; + } + + public GUIOutput getGuiOutput(int index) { + return guiOutputs[index]; + } + + public Pane getContentPane() { + return content; + } + +} diff --git a/src/jcgp/gui/population/GUIGene.java b/src/jcgp/gui/population/GUIGene.java new file mode 100644 index 0000000..ef44110 --- /dev/null +++ b/src/jcgp/gui/population/GUIGene.java @@ -0,0 +1,56 @@ +package jcgp.gui.population; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Group; +import javafx.scene.shape.Circle; +import javafx.scene.text.Text; +import jcgp.population.Gene; + +enum GUIGeneState { + NEUTRAL, + HOVER, + INDIRECT_HOVER, + ACTIVE_HOVER, + SOURCE, + TARGET, + NO_CHANGE_TARGET, + FORBIDDEN_TARGET +} + +public abstract class GUIGene extends Group { + + public static final double NODE_RADIUS = 30; + public static final double SPACING = 15; + + public static final double THETA = Math.PI / 1.4; + public static final double SOCKET_RADIUS = Math.sqrt(NODE_RADIUS) / 1.8; + + public static final double NODE_TEXT = 0; + + protected Text text; + protected Circle mainCircle; + + protected SimpleObjectProperty<GUIGeneState> stateProperty = new SimpleObjectProperty<GUIGeneState>(GUIGeneState.NEUTRAL); + + protected ChromosomePane parent; + + public SimpleObjectProperty<GUIGeneState> stateProperty() { + return stateProperty; + } + + public void setState(GUIGeneState newState) { + stateProperty.set(newState); + } + + public void showText(boolean value) { + text.setVisible(value); + } + + public abstract Gene getGene(); + + public abstract void setConnections(GUIGeneState newState); + + public abstract void resetState(); + + +} diff --git a/src/jcgp/gui/population/GUIInput.java b/src/jcgp/gui/population/GUIInput.java new file mode 100644 index 0000000..6f7a523 --- /dev/null +++ b/src/jcgp/gui/population/GUIInput.java @@ -0,0 +1,193 @@ +package jcgp.gui.population; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.VPos; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import jcgp.GUI; +import jcgp.population.Input; +import jcgp.population.Node; +import jcgp.population.Output; + +public class GUIInput extends GUIGene { + + private Circle outputSocket; + private Input input; + + public GUIInput(ChromosomePane parentRef, final Input input) { + + this.parent = parentRef; + this.input = input; + + relocate(NODE_RADIUS, + (input.getIndex() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + + mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white")); + mainCircle.setStroke(Paint.valueOf("black")); + + text = new Text("I: " + input.getIndex()); + text.setFont(Font.font("Arial", 12)); + text.setTextOrigin(VPos.CENTER); + text.setTextAlignment(TextAlignment.CENTER); + text.setWrappingWidth(NODE_RADIUS * 2); + text.setX(-NODE_RADIUS); + text.setVisible(true); + + outputSocket = new Circle(NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white")); + outputSocket.setId(String.valueOf(0)); + outputSocket.setStroke(Paint.valueOf("black")); + + getChildren().addAll(mainCircle, text, outputSocket); + + /* + * Mouse event handlers on whole gene + */ + addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has entered this node, react appropriately + // this happens even if we are the source of the drag + if (event.getGestureSource() instanceof GUINode) { + Node source = ((GUINode) event.getGestureSource()).getGene(); + for (int i = 0; i < (int) GUI.resources.get("arity"); i++) { + if (input == source.getConnection(i)) { + stateProperty.set(GUIGeneState.NO_CHANGE_TARGET); + return; + } + } + } else if (event.getGestureSource() instanceof GUIOutput) { + Output source = ((GUIOutput) event.getGestureSource()).getGene(); + if (((GUIGene) event.getSource()).getGene() == source.getSource()) { + ((GUIGene) event.getSource()).setState(GUIGeneState.NO_CHANGE_TARGET); + } + } + stateProperty.set(GUIGeneState.TARGET); + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the user is dragging over this node, react appropriately + // this happens even if we are the source of the drag + + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has exited this node, react appropriately + // this happens even if we are the source of the drag + if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } else { + stateProperty.set(GUIGeneState.NEUTRAL); + } + } + + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // set states to reflect the new situation + ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL); + stateProperty.set(GUIGeneState.HOVER); + // the user released the drag gesture on this node, react appropriately + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).setChangingConnection(input); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).getGene().setConnection(0, input); + } + } + }); + + addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has entered this node without dragging, or it is dragging and this is the source + if (stateProperty.get() == GUIGeneState.NEUTRAL) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } + } + }); + + addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has left this node without dragging, or it is dragging and this is the source + if (stateProperty.get() == GUIGeneState.INDIRECT_HOVER) { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + } + }); + + stateProperty.addListener(new ChangeListener<GUIGeneState>() { + @Override + public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) { + + switch (newValue) { + case ACTIVE_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case FORBIDDEN_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR)); + break; + case NO_CHANGE_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case TARGET: + mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR)); + break; + default: + break; + + } + } + }); + + } + + @Override + public Input getGene() { + return input; + } + + /** + * Set all connections to a given state. + * + * @param newState the state to set connections to. + */ + @Override + public void setConnections(GUIGeneState newState) { + // nothing + } + + @Override + public void resetState() { + stateProperty.set(GUIGeneState.NEUTRAL); + + } +} diff --git a/src/jcgp/gui/population/GUINode.java b/src/jcgp/gui/population/GUINode.java new file mode 100644 index 0000000..4350e52 --- /dev/null +++ b/src/jcgp/gui/population/GUINode.java @@ -0,0 +1,417 @@ +package jcgp.gui.population; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.VPos; +import javafx.scene.control.Label; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import jcgp.CGP.Resources; +import jcgp.GUI; +import jcgp.function.Function; +import jcgp.population.Connection; +import jcgp.population.Input; +import jcgp.population.Node; +import jcgp.population.Output; + +public class GUINode extends GUIGene { + + private Circle[] sockets; + private Circle output; + + private Line[] lines; + + private Label connectionNumber; + + private Node node; + + private int connectionIndex; + + public GUINode(ChromosomePane parentRef, final Node node, Resources resources, Line[] connectionLines) { + + // store references + this.parent = parentRef; + this.node = node; + this.lines = connectionLines; + + // move the GUIGene to the right position + relocate(((node.getColumn() + 1) * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS, + (node.getRow() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + + // set the line ends correctly + updateLines(); + + connectionNumber = new Label(); + connectionNumber.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); "); + connectionNumber.setVisible(false); + + output = new Circle(NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white")); + output.setStroke(Paint.valueOf("black")); + + mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white")); + mainCircle.setStroke(Paint.valueOf("black")); + + text = new Text(node.getFunction().getName()); + this.node.functionProperty().addListener(new ChangeListener<Function>() { + @Override + public void changed(ObservableValue<? extends Function> observable, + Function oldValue, Function newValue) { + text.setText(newValue.getName()); + } + }); + text.setFont(Font.font("Arial", 12)); + text.setTextOrigin(VPos.CENTER); + text.setTextAlignment(TextAlignment.CENTER); + text.setWrappingWidth(NODE_RADIUS * 2); + text.setX(-NODE_RADIUS); + text.setVisible(true); + + sockets = new Circle[(int) resources.get("arity")]; + double angle, xPos, yPos; + for (int l = 0; l < sockets.length; l++) { + angle = ((((double) (l + 1)) / ((double) ((int) resources.get("arity") + 1))) * THETA) - (THETA / 2); + xPos = -Math.cos(angle) * NODE_RADIUS; + yPos = Math.sin(angle) * NODE_RADIUS; + + sockets[l] = new Circle(xPos, yPos, GUIGene.SOCKET_RADIUS, Paint.valueOf("white")); + sockets[l].setId(String.valueOf(l)); + sockets[l].setStroke(Paint.valueOf("black")); + + final Circle s = sockets[l]; + final int index = l; + + /* + * Mouse event handlers on sockets + * + */ + s.addEventFilter(MouseDragEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // the mouse has been dragged out of the socket, this means a full drag is in progress + startFullDrag(); + } + }); + + s.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user is hovering over connection socket + connectionNumber.setText("C: " + s.getId()); + connectionNumber.relocate(s.getCenterX() + 5, s.getCenterY() - 10); + connectionNumber.setVisible(true); + } + }); + + s.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user exits the connection socket + connectionNumber.setVisible(false); + } + }); + + s.addEventFilter(MouseDragEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + stateProperty.set(GUIGeneState.SOURCE); + connectionIndex = index; + } + }); + + s.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lines[connectionIndex].setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX()); + lines[connectionIndex].setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY()); + } + }); + + s.addEventFilter(MouseDragEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (event.isDragDetect()) { + // mouse was released before dragging out of the socket + updateLine(index); + stateProperty.set(GUIGeneState.HOVER); + } + } + }); + } + + /* + * Mouse event handlers on whole gene + */ + addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has entered this node, react appropriately + // this happens even if we are the source of the drag + if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) { + if (event.getGestureSource() instanceof GUINode) { + Node source = ((GUINode) event.getGestureSource()).getGene(); + for (int i = 0; i < lines.length; i++) { + if (node == source.getConnection(i)) { + stateProperty.set(GUIGeneState.NO_CHANGE_TARGET); + return; + } + } + } else if (event.getGestureSource() instanceof GUIOutput) { + Output source = ((GUIOutput) event.getGestureSource()).getGene(); + if (((GUIGene) event.getSource()).getGene() == source.getSource()) { + ((GUIGene) event.getSource()).setState(GUIGeneState.NO_CHANGE_TARGET); + } + } + stateProperty.set(GUIGeneState.TARGET); + } else { + stateProperty.set(GUIGeneState.FORBIDDEN_TARGET); + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the user is dragging over this node, react appropriately + // this happens even if we are the source of the drag + + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has exited this node, react appropriately + // this happens even if we are the source of the drag + if (event.isPrimaryButtonDown()) { + if (event.getGestureSource() == event.getSource()) { + stateProperty.set(GUIGeneState.SOURCE); + } else { + if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } else { + stateProperty.set(GUIGeneState.NEUTRAL); + } + } + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // set states to reflect the new situation + ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL); + stateProperty.set(GUIGeneState.HOVER); + // the user released the drag gesture on this node, react appropriately + if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) { + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).setChangingConnection(node); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).getGene().setConnection(0, node); + } + } + if (event.getGestureSource() instanceof GUINode) { + ((GUINode) event.getGestureSource()).updateLines(); + } else if (event.getGestureSource() instanceof GUIOutput) { + ((GUIOutput) event.getGestureSource()).updateLine(); + } + + } + }); + + + addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has entered this node without dragging, or it is dragging and this is the source + if (stateProperty.get() == GUIGeneState.NEUTRAL) { + stateProperty.set(GUIGeneState.HOVER); + } + } + }); + + addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has left this node without dragging, or it is dragging and this is the source + if (stateProperty.get() == GUIGeneState.HOVER) { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + } + }); + + getChildren().addAll(mainCircle, text); + getChildren().addAll(sockets); + getChildren().addAll(output, connectionNumber); + + stateProperty.addListener(new ChangeListener<GUIGeneState>() { + @Override + public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) { + + switch (newValue) { + case ACTIVE_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + showLines(true); + setConnections(GUIGeneState.ACTIVE_HOVER); + break; + case FORBIDDEN_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + showLines(true); + setConnections(GUIGeneState.INDIRECT_HOVER); + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR)); + showLines(false); + if (oldValue == GUIGeneState.ACTIVE_HOVER) { + setConnections(GUIGeneState.NEUTRAL); + } + break; + case NO_CHANGE_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case TARGET: + mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR)); + break; + default: + break; + + } + } + }); + + for (int c = 0; c < lines.length; c++) { + final int i = c; + node.connections().get(c).addListener(new ChangeListener<Connection>() { + @Override + public void changed(ObservableValue<? extends Connection> observable, + Connection oldValue, Connection newValue) { + updateLine(i); + } + }); + } + + } + + + private boolean isAllowed(GUIGene source, GUIGene target) { + if (source instanceof GUINode) { + // if the source is a node, all inputs and some nodes are valid + if (target instanceof GUIInput) { + return true; + } else if (target instanceof GUINode) { + // target and source are nodes, let's look at levels back + Node t = ((GUINode) target).getGene(), s = ((GUINode) source).getGene(); + if (s.getColumn() - t.getColumn() > 0 && s.getColumn() - t.getColumn() <= (int) GUI.resources.get("levelsBack")) { + return true; + } + return false; + } else if (target instanceof GUIOutput) { + return false; + } else { + throw new ClassCastException("Target was neither GUINode nor GUIInput nor GUIOutput."); + } + } else if (source instanceof GUIOutput) { + // if the source is an output, any node or input is valid + if (target instanceof GUINode || target instanceof GUIInput) { + return true; + } else if (target instanceof GUIOutput) { + return false; + } else { + throw new ClassCastException("Target was neither GUINode nor GUIInput nor GUIOutput."); + } + } + // if the source was neither node nor output, something bad is happening + throw new ClassCastException("Source was neither GUINode nor GUIOutput."); + } + + + /** + * Place the end of the specified line on the output of the associated connection. + * + * @param index the line to be updated. + */ + public void updateLine(int index) { + if (node.getConnection(index) instanceof Node) { + int row = ((Node) node.getConnection(index)).getRow(), + column = ((Node) node.getConnection(index)).getColumn(); + lines[index].setEndX(((column + 1) * (2 * NODE_RADIUS + SPACING)) + 2 * NODE_RADIUS); + lines[index].setEndY((row * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + } else if (node.getConnection(index) instanceof Input) { + int inputIndex = ((Input) node.getConnection(index)).getIndex(); + lines[index].setEndX(2 * NODE_RADIUS); + lines[index].setEndY(inputIndex * (2 * NODE_RADIUS + SPACING) + NODE_RADIUS); + } + } + + /** + * Updates the end of all lines to match the associated connections. + */ + public void updateLines() { + for (int c = 0; c < lines.length; c++) { + updateLine(c); + } + } + + /** + * Toggle visibility of all connection lines. + * + * @param value whether to show the lines or not. + */ + private void showLines(boolean value) { + for (int i = 0; i < lines.length; i++) { + lines[i].setVisible(value); + } + } + + @Override + public Node getGene() { + return node; + } + + + @Override + public void setConnections(GUIGeneState newState) { + for (int i = 0; i < lines.length; i++) { + if (node.getConnection(i) instanceof Node) { + parent.getGuiNode(((Node) node.getConnection(i)).getRow(), ((Node) node.getConnection(i)).getColumn()).setState(newState); + } else if (node.getConnection(i) instanceof Input) { + parent.getGuiInput(((Input) node.getConnection(i)).getIndex()).setState(newState); + } + } + } + + /** + * This method sets the connection currently being changed to the + * specified value. + */ + public void setChangingConnection(Connection newConnection) { + node.setConnection(connectionIndex, newConnection); + } + + + @Override + public void resetState() { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + +} diff --git a/src/jcgp/gui/population/GUIOutput.java b/src/jcgp/gui/population/GUIOutput.java new file mode 100644 index 0000000..602a022 --- /dev/null +++ b/src/jcgp/gui/population/GUIOutput.java @@ -0,0 +1,277 @@ +package jcgp.gui.population; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.VPos; +import javafx.scene.control.Label; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; +import jcgp.GUI; +import jcgp.CGP.Resources; +import jcgp.population.Connection; +import jcgp.population.Input; +import jcgp.population.Node; +import jcgp.population.Output; + +public class GUIOutput extends GUIGene { + + private Circle socket; + + private Label connectionLabel; + + private Line sourceLine; + + private Output output; + + public GUIOutput(ChromosomePane parentRef, final Output output, Resources resources, Line line) { + + this.parent = parentRef; + this.output = output; + this.sourceLine = line; + + relocate((((int) resources.get("columns") + 1) * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS, + (output.getIndex() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + + // set the line ends correctly + updateLine(); + + mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white")); + mainCircle.setStroke(Paint.valueOf("black")); + + text = new Text("O: " + output.getIndex()); + text.setFont(Font.font("Arial", 12)); + text.setTextOrigin(VPos.CENTER); + text.setTextAlignment(TextAlignment.CENTER); + text.setWrappingWidth(NODE_RADIUS * 2); + text.setX(-NODE_RADIUS); + text.setVisible(true); + + socket = new Circle(-NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white")); + socket.setId(String.valueOf(0)); + socket.setStroke(Paint.valueOf("black")); + + connectionLabel = new Label("S"); + connectionLabel.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); "); + connectionLabel.relocate(socket.getCenterX() + 5, socket.getCenterY() - 10); + connectionLabel.setVisible(false); + + /* + * Mouse event handlers on sockets + * + */ + socket.addEventFilter(MouseDragEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // the mouse has been dragged out of the socket, this means a full drag is in progress + startFullDrag(); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user is hovering over connection socket + connectionLabel.setVisible(true); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // user exits the connection socket + connectionLabel.setVisible(false); + } + }); + + socket.addEventFilter(MouseDragEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + stateProperty.set(GUIGeneState.SOURCE); + } + }); + + socket.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + sourceLine.setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX()); + sourceLine.setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY()); + } + }); + + socket.addEventFilter(MouseDragEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (event.isDragDetect()) { + // mouse was released before dragging out of the socket + updateLine(); + stateProperty.set(GUIGeneState.HOVER); + } + } + }); + + + /* + * Mouse event handlers on whole gene + */ + addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has entered this node, react appropriately + stateProperty.set(GUIGeneState.FORBIDDEN_TARGET); + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the user is dragging over this node, react appropriately + // this happens even if we are the source of the drag + + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // the drag has exited this node, react appropriately + // this happens even if we are the source of the drag + if (event.isPrimaryButtonDown()) { + if (event.getGestureSource() == event.getSource()) { + stateProperty.set(GUIGeneState.SOURCE); + } else { + if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) { + stateProperty.set(GUIGeneState.INDIRECT_HOVER); + } else { + stateProperty.set(GUIGeneState.NEUTRAL); + } + } + } + } + }); + + addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() { + @Override + public void handle(MouseDragEvent event) { + // making a connection to an output is illegal + // set states to reflect the new situation + ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL); + ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL); + stateProperty.set(GUIGeneState.HOVER); + } + }); + + + addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has entered this node without dragging, or it is dragging and this is the source + if (stateProperty.get() == GUIGeneState.NEUTRAL) { + stateProperty.set(GUIGeneState.HOVER); + } + } + }); + + addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + // cursor has left this node without dragging, or it is dragging and this is the source + if (stateProperty.get() == GUIGeneState.HOVER) { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } + } + }); + + + getChildren().addAll(mainCircle, text, socket, connectionLabel); + + stateProperty.addListener(new ChangeListener<GUIGeneState>() { + @Override + public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) { + + + switch (newValue) { + case ACTIVE_HOVER: + break; + case FORBIDDEN_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR)); + break; + case HOVER: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + sourceLine.setVisible(true); + setConnections(GUIGeneState.ACTIVE_HOVER); + break; + case INDIRECT_HOVER: + mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR)); + break; + case NEUTRAL: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR)); + sourceLine.setVisible(false); + break; + case NO_CHANGE_TARGET: + mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR)); + break; + case SOURCE: + mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR)); + break; + case TARGET: + mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR)); + break; + default: + break; + + } + } + }); + + output.sourceProperty().addListener(new ChangeListener<Connection>() { + @Override + public void changed(ObservableValue<? extends Connection> observable, + Connection oldValue, Connection newValue) { + updateLine(); + } + }); + + } + + public void updateLine() { + if (output.getSource() instanceof Node) { + int row = ((Node) output.getSource()).getRow(), + column = ((Node) output.getSource()).getColumn(); + sourceLine.setEndX(((column + 1) * (2 * NODE_RADIUS + SPACING)) + 2 * NODE_RADIUS); + sourceLine.setEndY((row * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS); + } else if (output.getSource() instanceof Input) { + int inputIndex = ((Input) output.getSource()).getIndex(); + sourceLine.setEndX(2 * NODE_RADIUS); + sourceLine.setEndY(inputIndex * (2 * NODE_RADIUS + SPACING) + NODE_RADIUS); + } + } + + @Override + public Output getGene() { + return output; + } + + @Override + public void setConnections(GUIGeneState newState) { + if (output.getSource() instanceof Node) { + parent.getGuiNode(((Node) output.getSource()).getRow(), ((Node) output.getSource()).getColumn()).setState(newState); + } else if (output.getSource() instanceof Input) { + parent.getGuiInput(((Input) output.getSource()).getIndex()).setState(newState); + } + } + + @Override + public void resetState() { + stateProperty.set(GUIGeneState.NEUTRAL); + setConnections(GUIGeneState.NEUTRAL); + } +} diff --git a/src/jcgp/modules/Module.java b/src/jcgp/modules/Module.java index b8d44d2..5ce96b9 100644 --- a/src/jcgp/modules/Module.java +++ b/src/jcgp/modules/Module.java @@ -1,14 +1,11 @@ package jcgp.modules; +import java.util.HashMap; + +import jcgp.CGP.Resources; import jcgp.parameters.Parameter; -import jcgp.parameters.Parameters; -public abstract class Module { +public interface Module { - /** - * Register a new parameter - */ - protected final void registerParameter(String key, Parameter value) { - Parameters.add(key, value); - }; + public HashMap<String, Parameter> activate(Resources parameters); } diff --git a/src/jcgp/modules/ea/EvolutionaryAlgorithm.java b/src/jcgp/modules/ea/EvolutionaryAlgorithm.java index 8de8c87..d3fa709 100644 --- a/src/jcgp/modules/ea/EvolutionaryAlgorithm.java +++ b/src/jcgp/modules/ea/EvolutionaryAlgorithm.java @@ -1,12 +1,14 @@ package jcgp.modules.ea; +import jcgp.CGP.Resources; +import jcgp.modules.Module; import jcgp.modules.mutator.Mutator; import jcgp.population.Chromosome; import jcgp.population.Population; -public interface EvolutionaryAlgorithm { +public interface EvolutionaryAlgorithm extends Module { - public abstract void evolve(Population population, Mutator mutator); + public abstract void evolve(Population population, Mutator mutator, Resources parameters); public abstract Chromosome getFittestChromosome(); diff --git a/src/jcgp/modules/ea/StandardEA.java b/src/jcgp/modules/ea/StandardEA.java index 2db8776..f2473f5 100644 --- a/src/jcgp/modules/ea/StandardEA.java +++ b/src/jcgp/modules/ea/StandardEA.java @@ -1,14 +1,16 @@ package jcgp.modules.ea; +import java.util.HashMap; + +import jcgp.CGP.Resources; import jcgp.modules.mutator.Mutator; -import jcgp.parameters.Parameters; import jcgp.parameters.IntegerParameter; -import jcgp.parameters.BooleanParameter; +import jcgp.parameters.Parameter; import jcgp.population.Chromosome; import jcgp.population.Population; /** - * (1 + λ) EA. + * (μ + λ) EA. * * * @author Eduardo Pedroni @@ -17,28 +19,38 @@ import jcgp.population.Population; public class StandardEA implements EvolutionaryAlgorithm { private Chromosome fittestChromosome; + + private IntegerParameter parents, offspring; + private HashMap<String, Parameter> localParameters; + + public StandardEA() { + parents = new IntegerParameter(1, "Parents"); + offspring = new IntegerParameter(4, "Offspring"); + + localParameters = new HashMap<String, Parameter>(); + + localParameters.put("mu", parents); + localParameters.put("lambda", offspring); + } @Override - public void evolve(Population population, Mutator mutator) { + public void evolve(Population population, Mutator mutator, Resources parameters) { // select fittest chromosome int fittest = 0; - for (int i = 1; i < ((IntegerParameter) Parameters.get("population")).getValue(); i++) { + for (int i = 1; i < (int) parameters.get("popSize"); i++) { if (population.getChromosome(i).getFitness() >= population.getChromosome(fittest).getFitness()) { fittest = i; } } fittestChromosome = population.getChromosome(fittest); population.setBestIndividual(fittest); - if (((BooleanParameter) Parameters.get("debug")).getValue()) { - System.out.println("Best fitness: " + fittestChromosome.getFitness()); - } // create copies of fittest chromosome, mutate them Chromosome fc = population.getChromosome(fittest); - for (int i = 0; i < ((IntegerParameter) Parameters.get("population")).getValue(); i++) { + for (int i = 0; i < (int) parameters.get("popSize"); i++) { if (i != fittest) { population.getChromosome(i).copyConnections(fc); - mutator.mutate(population.getChromosome(i)); + mutator.mutate(population.getChromosome(i), parameters); } } } @@ -47,4 +59,11 @@ public class StandardEA implements EvolutionaryAlgorithm { public Chromosome getFittestChromosome() { return fittestChromosome; } + + @Override + public HashMap<String, Parameter> activate(Resources parameters) { + parameters.setManagedParameter("popSize", true); + + return localParameters; + } } diff --git a/src/jcgp/modules/fitness/FitnessFunction.java b/src/jcgp/modules/fitness/FitnessFunction.java index 8ed1b56..509c230 100644 --- a/src/jcgp/modules/fitness/FitnessFunction.java +++ b/src/jcgp/modules/fitness/FitnessFunction.java @@ -1,9 +1,11 @@ package jcgp.modules.fitness; +import jcgp.CGP.Resources; +import jcgp.modules.Module; import jcgp.population.Population; -public interface FitnessFunction { +public interface FitnessFunction extends Module { - public void evaluate(Population population); + public void evaluate(Population population, Resources resources); } diff --git a/src/jcgp/modules/fitness/TestCase.java b/src/jcgp/modules/fitness/TestCase.java index 0cb09f1..081a257 100644 --- a/src/jcgp/modules/fitness/TestCase.java +++ b/src/jcgp/modules/fitness/TestCase.java @@ -1,26 +1,13 @@ package jcgp.modules.fitness; -import jcgp.exceptions.ParameterMismatchException; -import jcgp.parameters.Parameters; - public class TestCase { private Object[] inputs; private Object[] outputs; - public TestCase(Object[] inputs, Object[] outputs) throws ParameterMismatchException { - if (inputs.length == Parameters.getInputs()) { - this.inputs = inputs; - } else { - throw new ParameterMismatchException(); - } - - if (outputs.length == Parameters.getOutputs()) { - this.outputs = outputs; - } else { - throw new ParameterMismatchException(); - } - + public TestCase(Object[] inputs, Object[] outputs) { + this.inputs = inputs; + this.outputs = outputs; } public Object getInput(int index) { diff --git a/src/jcgp/modules/fitness/TestCaseEvaluator.java b/src/jcgp/modules/fitness/TestCaseEvaluator.java new file mode 100644 index 0000000..1dade74 --- /dev/null +++ b/src/jcgp/modules/fitness/TestCaseEvaluator.java @@ -0,0 +1,34 @@ +package jcgp.modules.fitness; + +import java.util.HashMap; + +import jcgp.CGP.Resources; +import jcgp.parameters.Parameter; +import jcgp.population.Population; + +public class TestCaseEvaluator implements FitnessFunction { + + @Override + public void evaluate(Population population, Resources resources) { + // for every chromosome in the population + for (int i = 0; i < (int) resources.get("popSize"); i++) { + int fitness = 0; + // for every test case + for (int t = 0; t < resources.getTestCaseCount(); t++) { + population.getChromosome(i).setInputs(resources.getTestCase(t).getInputs()); + // check every output + for (int o = 0; o < (int) resources.get("outputs"); o++) { + if (population.getChromosome(i).getOutput(o).calculate() == resources.getTestCase(t).getOutput(o)) { + fitness++; + } + } + } + population.getChromosome(i).setFitness(fitness); + } + } + + @Override + public HashMap<String, Parameter> activate(Resources parameters) { + return new HashMap<String, Parameter>(); + } +} diff --git a/src/jcgp/modules/fitness/TruthTableEvaluator.java b/src/jcgp/modules/fitness/TruthTableEvaluator.java deleted file mode 100644 index a69de96..0000000 --- a/src/jcgp/modules/fitness/TruthTableEvaluator.java +++ /dev/null @@ -1,30 +0,0 @@ -package jcgp.modules.fitness; - -import jcgp.TruthTable; -import jcgp.parameters.Parameters; -import jcgp.population.Population; - -public class TruthTableEvaluator implements FitnessFunction { - - @Override - public void evaluate(Population population) { - // for every chromosome in the population - for (int i = 0; i < Parameters.getPopulationSize(); i++) { - int fitness = 0; - // for every test case - for (int t = 0; t < TruthTable.getTestCaseCount(); t++) { - population.getChromosome(i).setInputs(TruthTable.getTestCase(t).getInputs()); - // check every output - for (int o = 0; o < Parameters.getOutputs(); o++) { - if (population.getChromosome(i).getOutput(o).calculate() == TruthTable.getTestCase(t).getOutput(o)) { - fitness++; - } - } - } - population.getChromosome(i).setFitness(fitness); - if (Parameters.getDebug()) { - System.out.println("active nodes: " + population.getChromosome(i).getActiveNodes().size()); - } - } - } -} diff --git a/src/jcgp/modules/function/FunctionSet.java b/src/jcgp/modules/function/FunctionSet.java deleted file mode 100644 index fb3724f..0000000 --- a/src/jcgp/modules/function/FunctionSet.java +++ /dev/null @@ -1,45 +0,0 @@ -package jcgp.modules.function; - -/** - * - * TODO: if function set flexibility is desired (i.e. add more functions as the program runs) - * an add function method should be created - * this would lead to concurrency problems, so tread lightly! - * - * - * @author Eduardo Pedroni - * - */ -public class FunctionSet { - private Function[] functionList; - private int maxArity = 0; - private String name; - - public FunctionSet(String name, Function ... functions) { - functionList = functions; - for (Function function : functionList) { - if (function.getArity() > maxArity) { - maxArity = function.getArity(); - } - } - - this.name = name; - - } - - public int getFunctionCount() { - return functionList.length; - } - - public Function getFunction(int index) { - return functionList[index]; - } - - public int getMaxArity(){ - return maxArity; - } - - public String getName() { - return name; - } - }
\ No newline at end of file diff --git a/src/jcgp/modules/mutator/Mutator.java b/src/jcgp/modules/mutator/Mutator.java index 10df8cd..5234f45 100644 --- a/src/jcgp/modules/mutator/Mutator.java +++ b/src/jcgp/modules/mutator/Mutator.java @@ -1,9 +1,11 @@ package jcgp.modules.mutator; +import jcgp.CGP.Resources; +import jcgp.modules.Module; import jcgp.population.Chromosome; -public interface Mutator { +public interface Mutator extends Module { - void mutate(Chromosome chromosome); + void mutate(Chromosome chromosome, Resources parameters); } diff --git a/src/jcgp/modules/mutator/PointMutator.java b/src/jcgp/modules/mutator/PointMutator.java index b06d678..ea1ed32 100644 --- a/src/jcgp/modules/mutator/PointMutator.java +++ b/src/jcgp/modules/mutator/PointMutator.java @@ -1,36 +1,49 @@ package jcgp.modules.mutator; -import jcgp.Utilities; -import jcgp.parameters.IntegerParameter; -import jcgp.parameters.Parameters; +import java.util.HashMap; + +import jcgp.parameters.DoubleParameter; +import jcgp.parameters.Parameter; +import jcgp.CGP.Resources; import jcgp.population.Chromosome; import jcgp.population.MutableElement; import jcgp.population.Node; import jcgp.population.Output; public class PointMutator implements Mutator { - + + private Parameter mutationRate; + private HashMap<String, Parameter> localParameters; + public PointMutator() { - Parameters.add("mutRate", new IntegerParameter(10, "Mutation rate")); + mutationRate = new DoubleParameter(0.5, "Percent mutation"); + + localParameters = new HashMap<String, Parameter>(); + localParameters.put("mutRate", mutationRate); } - + @Override - public void mutate(Chromosome chromosome) { - int mutations = (int) (((int) Parameters.get("mutRate").getValue()) * ((((double) Parameters.get("nodes").getValue()) + ((double) Parameters.get("outputs").getValue())) / 100)); - + public void mutate(Chromosome chromosome, Resources resources) { + int mutations = (int) Math.ceil((((double) mutationRate.getValue()) * (((((Integer) resources.get("nodes")).doubleValue() + ((Integer) resources.get("outputs")).doubleValue())) / (double) 100))); for (int i = 0; i < mutations; i++) { MutableElement m = chromosome.getRandomMutableElement(); if (m instanceof Output) { - m.setConnection(chromosome.getRandomConnection()); + m.setConnection(0, chromosome.getRandomConnection()); } else if (m instanceof Node) { - int geneType = Utilities.getRandomInt(1 + ((int) Parameters.get("Max arity").getValue())); + int geneType = resources.getRandomInt(1 + ((int) resources.get("arity"))); if (geneType < 1) { - ((Node) m).setFunction(Utilities.getRandomFunction()); + ((Node) m).setFunction(resources.getRandomFunction()); } else { - m.setConnection(chromosome.getRandomConnection(((Node) m).getColumn())); + m.setConnection(resources.getRandomInt((int) resources.get("arity")), chromosome.getRandomConnection(((Node) m).getColumn())); } } } } + + @Override + public HashMap<String, Parameter> activate(Resources parameters) { + return localParameters; + } + } diff --git a/src/jcgp/parameters/BooleanParameter.java b/src/jcgp/parameters/BooleanParameter.java index aff0bc5..0f339a1 100644 --- a/src/jcgp/parameters/BooleanParameter.java +++ b/src/jcgp/parameters/BooleanParameter.java @@ -3,34 +3,30 @@ package jcgp.parameters; import javafx.beans.property.SimpleBooleanProperty; public class BooleanParameter extends Parameter { - - SimpleBooleanProperty value; - + public BooleanParameter(boolean value, String name) { + super(name); this.value = new SimpleBooleanProperty(value); - this.name = name; } public BooleanParameter(boolean value, String name, boolean managed, boolean hidden) { + super(name, managed, hidden); this.value = new SimpleBooleanProperty(value); - this.name = name; - this.managed = managed; - this.hidden = hidden; } @Override - public Object getValue() { - return this.value.get(); + public Boolean getValue() { + return ((SimpleBooleanProperty) this.value).get(); } @Override public SimpleBooleanProperty valueProperty() { - return value; + return (SimpleBooleanProperty) value; } @Override public void setValue(Object value) { - this.value.set((boolean) value); + ((SimpleBooleanProperty) this.value).set((boolean) value); } } diff --git a/src/jcgp/parameters/DoubleParameter.java b/src/jcgp/parameters/DoubleParameter.java index 2725457..ade99cc 100644 --- a/src/jcgp/parameters/DoubleParameter.java +++ b/src/jcgp/parameters/DoubleParameter.java @@ -3,37 +3,30 @@ package jcgp.parameters; import javafx.beans.property.SimpleDoubleProperty; public class DoubleParameter extends Parameter { - - SimpleDoubleProperty value; - + public DoubleParameter(double value, String name) { + super(name); this.value = new SimpleDoubleProperty(value); - this.name = name; } public DoubleParameter(double value, String name, boolean managed, boolean hidden) { + super(name, managed, hidden); this.value = new SimpleDoubleProperty(value); - this.name = name; - this.managed = managed; - this.hidden = hidden; - } - - public void setValue(double value) { - this.value.set(value); } @Override - public Object getValue() { - return this.value.get(); + public Double getValue() { + return ((SimpleDoubleProperty) value).get(); } + @Override public SimpleDoubleProperty valueProperty() { - return value; + return (SimpleDoubleProperty) value; } @Override public void setValue(Object value) { - this.value.set((double) value); + ((SimpleDoubleProperty) this.value).set((double) value); } diff --git a/src/jcgp/parameters/IntegerParameter.java b/src/jcgp/parameters/IntegerParameter.java index f0109f7..cc60179 100644 --- a/src/jcgp/parameters/IntegerParameter.java +++ b/src/jcgp/parameters/IntegerParameter.java @@ -3,39 +3,30 @@ package jcgp.parameters; import javafx.beans.property.SimpleIntegerProperty; public class IntegerParameter extends Parameter { - - SimpleIntegerProperty value; public IntegerParameter(int value, String name) { + super(name); this.value = new SimpleIntegerProperty(value); - this.name = name; } public IntegerParameter(int value, String name, boolean managed, boolean hidden) { + super(name, managed, hidden); this.value = new SimpleIntegerProperty(value); - this.name = name; - this.managed = managed; - this.hidden = hidden; - } - - public void setValue(int value) { - this.value.set(value); } @Override - public Object getValue() { - return this.value.get(); + public Integer getValue() { + return ((SimpleIntegerProperty) this.value).get(); } @Override public SimpleIntegerProperty valueProperty() { - return value; + return (SimpleIntegerProperty) value; } @Override public void setValue(Object value) { - this.value.set((int) value); - + ((SimpleIntegerProperty) this.value).set((int) value); } } diff --git a/src/jcgp/parameters/Parameter.java b/src/jcgp/parameters/Parameter.java index 64b19c8..fcff9fd 100644 --- a/src/jcgp/parameters/Parameter.java +++ b/src/jcgp/parameters/Parameter.java @@ -1,13 +1,28 @@ package jcgp.parameters; import javafx.beans.property.Property; - public abstract class Parameter { protected boolean managed = false; protected boolean hidden = false; + + protected boolean originalManaged = false; + protected boolean originalHidden = false; + + protected Property<?> value; + protected String name; + public Parameter(String name, boolean managed, boolean hidden) { + this.name = name; + this.managed = originalManaged = managed; + this.hidden = originalHidden = hidden; + } + + public Parameter(String name) { + this.name = name; + } + public void setManaged(boolean value) { managed = value; } @@ -32,6 +47,12 @@ public abstract class Parameter { public abstract void setValue(Object value); - @SuppressWarnings("rawtypes") - public abstract Property valueProperty(); + public Property<?> valueProperty() { + return value; + } + + public void reset() { + hidden = originalHidden; + managed = originalManaged; + } } diff --git a/src/jcgp/population/Chromosome.java b/src/jcgp/population/Chromosome.java index 1f0b312..a7f2ed3 100644 --- a/src/jcgp/population/Chromosome.java +++ b/src/jcgp/population/Chromosome.java @@ -2,11 +2,12 @@ package jcgp.population; import java.util.ArrayList; -import jcgp.Utilities; +import jcgp.CGP.Resources; import jcgp.exceptions.ParameterMismatchException; -import jcgp.parameters.Parameters; public class Chromosome { + + private Resources resources; private Input[] inputs; private Node[][] nodes; @@ -21,13 +22,12 @@ public class Chromosome { * Initialise a chromosome with the specified parameters. Random valid connections * are created. * - * @param outputs - * @param columns - * @param rows - * @param inputs * */ - public Chromosome() { + public Chromosome(Resources resources) { + // store a reference to the parameters + this.resources = resources; + // allocate memory for all elements of the chromosome instantiateElements(); // set random connections so that the chromosome can be evaluated @@ -42,6 +42,9 @@ public class Chromosome { * @param clone the chromosome to be copied */ public Chromosome(Chromosome clone) { + // store a reference to the parameters + this.resources = clone.getParameters(); + // allocate memory for all elements of the chromosome instantiateElements(); // initialise all connections based on argument @@ -52,20 +55,22 @@ public class Chromosome { * */ private void instantiateElements() { - inputs = new Input[((int) Parameters.get("inputs").getValue())]; - for (int i = 0; i < ((int) Parameters.get("inputs").getValue()); i++) { + inputs = new Input[((int) resources.get("inputs"))]; + for (int i = 0; i < ((int) resources.get("inputs")); i++) { inputs[i] = new Input(i); } + int arity = (int) resources.get("arity"); + // rows first - nodes = new Node[((int) Parameters.get("rows").getValue())][((int) Parameters.get("columns").getValue())]; - for (int r = 0; r < ((int) Parameters.get("rows").getValue()); r++) { - for (int c = 0; c < ((int) Parameters.get("columns").getValue()); c++) { - nodes[r][c] = new Node(this, r, c); + nodes = new Node[((int) resources.get("rows"))][((int) resources.get("columns"))]; + for (int r = 0; r < ((int) resources.get("rows")); r++) { + for (int c = 0; c < ((int) resources.get("columns")); c++) { + nodes[r][c] = new Node(this, r, c, arity); } } - outputs = new Output[((int) Parameters.get("outputs").getValue())]; - for (int o = 0; o < ((int) Parameters.get("outputs").getValue()); o++) { + outputs = new Output[((int) resources.get("outputs"))]; + for (int o = 0; o < ((int) resources.get("outputs")); o++) { outputs[o] = new Output(this, o); } } @@ -75,19 +80,21 @@ public class Chromosome { */ private void initialiseConnections() { + int arity = (int) resources.get("arity"); + // initialise nodes - [rows][columns] for (int r = 0; r < nodes.length; r++) { for (int c = 0; c < nodes[r].length; c++) { - Connection[] connections = new Connection[((int) Parameters.get("maxArity").getValue())]; + Connection[] connections = new Connection[arity]; for (int i = 0; i < connections.length; i++) { connections[i] = getRandomConnection(c); } - nodes[r][c].initialise(Utilities.getRandomFunction(), connections); + nodes[r][c].initialise(resources.getRandomFunction(), connections); } } for (Output output : outputs) { - output.setConnection(getRandomConnection()); + output.setConnection(0, getRandomConnection()); } } @@ -96,11 +103,13 @@ public class Chromosome { * @param clone */ public void copyConnections(Chromosome clone) { + int arity = (int) resources.get("arity"); + // copy nodes - [rows][columns] for (int r = 0; r < nodes.length; r++) { for (int c = 0; c < nodes[r].length; c++) { // make array of connections to initialise with - Connection[] connections = new Connection[((int) Parameters.get("maxArity").getValue())]; + Connection[] connections = new Connection[arity]; // populate with connections equivalent to clone Connection copyConnection; for (int i = 0; i < connections.length; i++) { @@ -123,9 +132,9 @@ public class Chromosome { for (int o = 0; o < outputs.length; o++) { copyOutput = clone.getOutput(o).getSource(); if (copyOutput instanceof Input) { - outputs[o].setConnection(inputs[((Input) copyOutput).getIndex()]); + outputs[o].setConnection(0, inputs[((Input) copyOutput).getIndex()]); } else if (copyOutput instanceof Node) { - outputs[o].setConnection(nodes[((Node) copyOutput).getRow()][((Node) copyOutput).getColumn()]); + outputs[o].setConnection(0, nodes[((Node) copyOutput).getRow()][((Node) copyOutput).getColumn()]); } else { // something bad happened System.out.println("Warning: Connection of subtype " + copyOutput.getClass().toString() + " is not explicitly handled by copy constructor."); @@ -177,7 +186,7 @@ public class Chromosome { */ public MutableElement getRandomMutableElement() { // choose output or node - int index = Utilities.getRandomInt(outputs.length + ((int) Parameters.get("rows").getValue()) * ((int) Parameters.get("columns").getValue())); + int index = resources.getRandomInt(outputs.length + ((int) resources.get("rows")) * ((int) resources.get("columns"))); if (index < outputs.length) { // outputs @@ -185,7 +194,7 @@ public class Chromosome { } else { // node index -= outputs.length; - return nodes[index / ((int) Parameters.get("columns").getValue())][index % ((int) Parameters.get("columns").getValue())]; + return nodes[index / ((int) resources.get("columns"))][index % ((int) resources.get("columns"))]; } } @@ -199,11 +208,11 @@ public class Chromosome { */ public Connection getRandomConnection(int column) { // work out the allowed range obeying levels back - int allowedColumns = ((column >= ((int) Parameters.get("levelsBack").getValue())) ? ((int) Parameters.get("levelsBack").getValue()) : column); + int allowedColumns = ((column >= ((int) resources.get("levelsBack"))) ? ((int) resources.get("levelsBack")) : column); int offset = ((column - allowedColumns) * nodes.length) - inputs.length; // choose input or allowed node - int index = Utilities.getRandomInt(inputs.length + (nodes.length * allowedColumns)); + int index = resources.getRandomInt(inputs.length + (nodes.length * allowedColumns)); if (index < inputs.length) { // input return inputs[index]; @@ -226,7 +235,7 @@ public class Chromosome { */ public Connection getRandomConnection() { // choose output or node - int index = Utilities.getRandomInt(inputs.length + ((int) Parameters.get("columns").getValue()) * ((int) Parameters.get("rows").getValue())); + int index = resources.getRandomInt(inputs.length + ((int) resources.get("columns")) * ((int) resources.get("rows"))); if (index < inputs.length) { // outputs @@ -234,7 +243,7 @@ public class Chromosome { } else { // node index -= inputs.length; - return nodes[index / ((int) Parameters.get("columns").getValue())][index % ((int) Parameters.get("columns").getValue())]; + return nodes[index / ((int) resources.get("columns"))][index % ((int) resources.get("columns"))]; } } @@ -269,15 +278,15 @@ public class Chromosome { } public boolean compareTo(Chromosome chromosome) { - for (int r = 0; r < ((int) Parameters.get("rows").getValue()); r++) { - for (int c = 0; c < ((int) Parameters.get("columns").getValue()); c++) { + for (int r = 0; r < ((int) resources.get("rows")); r++) { + for (int c = 0; c < ((int) resources.get("columns")); c++) { if (!(nodes[r][c].copyOf(chromosome.getNode(r, c)))) { return false; } } } - for (int o = 0; o < ((int) Parameters.get("outputs").getValue()); o++) { + for (int o = 0; o < ((int) resources.get("outputs")); o++) { if (!(outputs[o].copyOf(chromosome.getOutput(o)))) { return false; } @@ -302,20 +311,26 @@ public class Chromosome { } public void printNodes() { - for (int r = 0; r < ((int) Parameters.get("rows").getValue()); r++) { + int arity = (int) resources.get("arity"); + + for (int r = 0; r < ((int) resources.get("rows")); r++) { System.out.print("r: " + r + "\t"); - for (int c = 0; c < ((int) Parameters.get("columns").getValue()); c++) { + for (int c = 0; c < ((int) resources.get("columns")); c++) { System.out.print("N: (" + r + ", " + c + ") "); - for (int i = 0; i < ((int) Parameters.get("maxArity").getValue()); i++) { + for (int i = 0; i < arity; i++) { System.out.print("C" + i + ": (" + nodes[r][c].getConnection(i).getDescription() + ") "); } - System.out.print("F: " + nodes[r][c].getFunction().toString() + "\t"); + System.out.print("F: " + nodes[r][c].getFunction().getName() + "\t"); } System.out.print("\n"); } - for (int o = 0; o < ((int) Parameters.get("outputs").getValue()); o++) { + for (int o = 0; o < ((int) resources.get("outputs")); o++) { System.out.print("o: " + o + " (" + outputs[o].getSource().getDescription() + ")\t"); } } + + public Resources getParameters() { + return resources; + } } diff --git a/src/jcgp/population/Gene.java b/src/jcgp/population/Gene.java new file mode 100644 index 0000000..8865a01 --- /dev/null +++ b/src/jcgp/population/Gene.java @@ -0,0 +1,5 @@ +package jcgp.population; + +public abstract class Gene { + +} diff --git a/src/jcgp/population/Input.java b/src/jcgp/population/Input.java index 5c545d6..cfcb3ce 100644 --- a/src/jcgp/population/Input.java +++ b/src/jcgp/population/Input.java @@ -1,6 +1,6 @@ package jcgp.population; -public class Input implements Connection { +public class Input extends Gene implements Connection { private Object value = 0; private int index; diff --git a/src/jcgp/population/MutableElement.java b/src/jcgp/population/MutableElement.java index 114a4ab..12f6bd1 100644 --- a/src/jcgp/population/MutableElement.java +++ b/src/jcgp/population/MutableElement.java @@ -2,8 +2,16 @@ package jcgp.population; public interface MutableElement { - public void setConnection(Connection newConnection); - + /** + * This method sets the indexed connection to the specified new connection. + * Implementing classes may choose to ignore the given index (such as in the + * case of outputs, which only have one connection). + * + * @param index + * @param newConnection + */ + public void setConnection(int index, Connection newConnection); + /** * This method returns true if and only if:</br> * - the elements being compared are not the same instance;</br> @@ -15,7 +23,8 @@ public interface MutableElement { * The relationship computed by this method is:</br> * - symmetric: a.copyOf(b) == b.copyOf(a);</br> * - not reflexive: a.copyOf(a) returns false;</br> - * - not transitive: if a.copyOf(b) is true and b.copyOf(c) is true, a.copyOf(c) is not necessarily true;</br> + * - 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.</br> * * @param m * @return diff --git a/src/jcgp/population/Node.java b/src/jcgp/population/Node.java index 9fafe32..d7013d3 100644 --- a/src/jcgp/population/Node.java +++ b/src/jcgp/population/Node.java @@ -1,51 +1,58 @@ package jcgp.population; import java.util.ArrayList; -import java.util.Arrays; -import jcgp.Utilities; +import javafx.beans.property.SimpleObjectProperty; import jcgp.exceptions.InsufficientConnectionsException; -import jcgp.modules.function.Function; -import jcgp.parameters.Parameters; +import jcgp.function.Function; -public class Node implements MutableElement, Connection { +public class Node extends Gene implements MutableElement, Connection { - private Function function; - private Connection[] connections; + private SimpleObjectProperty<Function> function; + private ArrayList<SimpleObjectProperty<Connection>> connections; private int column, row; private Chromosome chromosome; - public Node(Chromosome chromosome, int row, int column) { + public Node(Chromosome chromosome, int row, int column, int arity) { + this.function = new SimpleObjectProperty<Function>(); this.chromosome = chromosome; this.column = column; this.row = row; - this.connections = new Connection[((int) Parameters.get("maxArity").getValue())]; + this.connections = new ArrayList<SimpleObjectProperty<Connection>>(arity); + for (int c = 0; c < arity; c++) { + connections.add(new SimpleObjectProperty<Connection>()); + } } @Override public Object getValue() { //System.out.print("Calculating node: (" + row + ", " + column + ") > "); - return function.run(Arrays.copyOfRange(connections, 0, function.getArity())); + Connection[] list = new Connection[function.get().getArity()]; + for (int i = 0; i < list.length; i++) { + list[i] = connections.get(i).get(); + } + + return function.get().run(list); } public void setFunction(Function newFunction) { - function = newFunction; + function.set(newFunction); chromosome.recomputeActiveNodes(); } @Override - public void setConnection(Connection newConnection) { - connections[Utilities.getRandomInt(connections.length)] = newConnection; + public void setConnection(int index, Connection newConnection) { + connections.get(index).set(newConnection); chromosome.recomputeActiveNodes(); } public void initialise(Function newFunction, Connection ... newConnections) throws InsufficientConnectionsException { - - function = newFunction; - - if (newConnections.length >= ((int) Parameters.get("maxArity").getValue())) { - connections = newConnections; + function.set(newFunction); + if (newConnections.length >= connections.size()) { + for (int i = 0; i < connections.size(); i++) { + connections.get(i).set(newConnections[i]); + } } else { throw new InsufficientConnectionsException(); } @@ -60,20 +67,28 @@ public class Node implements MutableElement, Connection { } public Function getFunction() { + return function.get(); + } + + public SimpleObjectProperty<Function> functionProperty() { return function; } + public ArrayList<SimpleObjectProperty<Connection>> connections() { + return connections; + } + public Connection getConnection(int index) { - return connections[index]; + return connections.get(index).get(); } public void getActive(ArrayList<Node> activeNodes) { if (!activeNodes.contains(this)) { activeNodes.add(this); } - for (int i = 0; i < function.getArity(); i++) { - if (connections[i] instanceof Node) { - ((Node) connections[i]).getActive(activeNodes); + for (int i = 0; i < function.get().getArity(); i++) { + if (connections.get(i).get() instanceof Node) { + ((Node) connections.get(i).get()).getActive(activeNodes); } } @@ -84,17 +99,17 @@ public class Node implements MutableElement, Connection { if (this != m) { if (m instanceof Node) { Node n = (Node) m; - if (function == n.getFunction()) { + if (function.get() == n.getFunction()) { if (column == n.getColumn() && row == n.getRow()) { - for (int i = 0; i < connections.length; i++) { - if (connections[i] != n.getConnection(i)) { - if (connections[i] instanceof Input && n.getConnection(i) instanceof Input) { - if (((Input) connections[i]).getIndex() != ((Input) n.getConnection(i)).getIndex()) { + for (int i = 0; i < connections.size(); i++) { + if (connections.get(i).get() != n.getConnection(i)) { + if (connections.get(i).get() instanceof Input && n.getConnection(i) instanceof Input) { + if (((Input) connections.get(i).get()).getIndex() != ((Input) n.getConnection(i)).getIndex()) { return false; } - } else if (connections[i] instanceof Node && n.getConnection(i) instanceof Node) { - if (((Node) connections[i]).getRow() != ((Node) n.getConnection(i)).getRow() && - ((Node) connections[i]).getColumn() != ((Node) n.getConnection(i)).getColumn()) { + } else if (connections.get(i).get() instanceof Node && n.getConnection(i) instanceof Node) { + if (((Node) connections.get(i).get()).getRow() != ((Node) n.getConnection(i)).getRow() && + ((Node) connections.get(i).get()).getColumn() != ((Node) n.getConnection(i)).getColumn()) { return false; } } else { diff --git a/src/jcgp/population/Output.java b/src/jcgp/population/Output.java index ccecae0..b7c6128 100644 --- a/src/jcgp/population/Output.java +++ b/src/jcgp/population/Output.java @@ -2,15 +2,18 @@ package jcgp.population; import java.util.ArrayList; -public class Output implements MutableElement { +import javafx.beans.property.SimpleObjectProperty; + +public class Output extends Gene implements MutableElement { - private Connection source; + private SimpleObjectProperty<Connection> source; private Chromosome chromosome; private int index; public Output(Chromosome chromosome, int index) { this.chromosome = chromosome; this.index = index; + this.source = new SimpleObjectProperty<Connection>(); } public Object calculate() { @@ -19,8 +22,8 @@ public class Output implements MutableElement { } @Override - public void setConnection(Connection newConnection) { - source = newConnection; + public void setConnection(int index, Connection newConnection) { + source.set(newConnection); chromosome.recomputeActiveNodes(); } @@ -29,12 +32,16 @@ public class Output implements MutableElement { } public Connection getSource() { + return source.get(); + } + + public SimpleObjectProperty<Connection> sourceProperty() { return source; } public void getActiveNodes(ArrayList<Node> activeNodes) { - if (source instanceof Node) { - ((Node) source).getActive(activeNodes); + if (source.get() instanceof Node) { + ((Node) source.get()).getActive(activeNodes); } } @@ -44,14 +51,14 @@ public class Output implements MutableElement { if (m instanceof Output) { Output o = (Output) m; if (index == o.getIndex()) { - if (source != o.getSource()) { - if (source instanceof Input && o.getSource() instanceof Input) { - if (((Input) source).getIndex() == ((Input) o.getSource()).getIndex()) { + if (source.get() != o.getSource()) { + if (source.get() instanceof Input && o.getSource() instanceof Input) { + if (((Input) source.get()).getIndex() == ((Input) o.getSource()).getIndex()) { return true; } - } else if (source instanceof Node && o.getSource() instanceof Node) { - if (((Node) source).getRow() == ((Node) o.getSource()).getRow() && - ((Node) source).getColumn() == ((Node) o.getSource()).getColumn()) { + } else if (source.get() instanceof Node && o.getSource() instanceof Node) { + if (((Node) source.get()).getRow() == ((Node) o.getSource()).getRow() && + ((Node) source.get()).getColumn() == ((Node) o.getSource()).getColumn()) { return true; } } diff --git a/src/jcgp/population/Population.java b/src/jcgp/population/Population.java index 5718515..bc9c9e8 100644 --- a/src/jcgp/population/Population.java +++ b/src/jcgp/population/Population.java @@ -1,14 +1,22 @@ package jcgp.population; -import jcgp.parameters.Parameters; +import jcgp.CGP.Resources; + public class Population { private Chromosome[] chromosomes; - private Chromosome bestIndividual; + private int fittest; + + public Population(Resources parameters) { + chromosomes = new Chromosome[((int) parameters.get("popSize"))]; + for (int c = 0; c < chromosomes.length; c++) { + chromosomes[c] = new Chromosome(parameters); + } + } - public Population(Chromosome parent) { - chromosomes = new Chromosome[((int) Parameters.get("population").getValue())]; + public Population(Chromosome parent, Resources parameters) { + chromosomes = new Chromosome[((int) parameters.get("popSize"))]; // make a clone for safety this.chromosomes[0] = new Chromosome(parent); // generate the rest of the individuals @@ -16,13 +24,6 @@ public class Population { chromosomes[c] = new Chromosome(chromosomes[0]); } } - - public Population() { - chromosomes = new Chromosome[((int) Parameters.get("population").getValue())]; - for (int c = 0; c < chromosomes.length; c++) { - chromosomes[c] = new Chromosome(); - } - } /** * Returns all chromosomes, parents first, then offspring. @@ -35,10 +36,10 @@ public class Population { } public void setBestIndividual(int index) { - bestIndividual = chromosomes[index]; + fittest = index; } public Chromosome getBestIndividual() { - return bestIndividual; + return chromosomes[fittest]; } } diff --git a/src/jcgp/tests/ChromosomeTests.java b/src/jcgp/tests/ChromosomeTests.java index c61785e..6323b88 100644 --- a/src/jcgp/tests/ChromosomeTests.java +++ b/src/jcgp/tests/ChromosomeTests.java @@ -1,13 +1,8 @@ package jcgp.tests; -import static org.junit.Assert.*; - -import java.util.Random; - -import jcgp.Utilities; -import jcgp.modules.function.Arithmetic; -import jcgp.modules.function.FunctionSet; -import jcgp.parameters.Parameters; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import jcgp.CGP.Resources; import jcgp.population.Chromosome; import jcgp.population.Connection; import jcgp.population.Input; @@ -45,30 +40,16 @@ import org.junit.Test; public class ChromosomeTests { private Chromosome chromosome; + private static Resources resources; @BeforeClass public static void setUpBeforeClass() { - // initialise function set - FunctionSet functionSet = new FunctionSet(new Arithmetic.Addition(), new Arithmetic.Subtraction()); - - // initialise utilities - Utilities.setResources(new Random(1234), functionSet); - - // initialise parameters - Parameters.setMutationRate(10); - Parameters.setTotalGenerations(10); - Parameters.setTotalRuns(5); - Parameters.setMaxArity(functionSet.getMaxArity()); + resources = new Resources(); } @Before public void setUp() throws Exception { - Parameters.setColumns(5); - Parameters.setRows(2); - Parameters.setInputs(2); - Parameters.setOutputs(4); - Parameters.setLevelsBack(5); - chromosome = new Chromosome(); + chromosome = new Chromosome(resources); } /** @@ -81,7 +62,7 @@ public class ChromosomeTests { // compare all elements, one by one // check outputs - for (int o = 0; o < Parameters.getOutputs(); o++) { + for (int o = 0; o < (int) resources.get("outputs"); o++) { // check that no cross-references exist between chromosomes assertTrue("Cloned chromosome contains a reference to a member of the original chromosome.", clone.getOutput(o) != chromosome.getOutput(o) && @@ -99,8 +80,8 @@ public class ChromosomeTests { } } // check nodes, rows first - for (int row = 0; row < Parameters.getRows(); row++) { - for (int column = 0; column < Parameters.getColumns(); column++) { + for (int row = 0; row < (int) resources.get("rows"); row++) { + for (int column = 0; column < (int) resources.get("columns"); column++) { // check that nodes are not pointers to the same instance assertTrue("Both chromosomes contain a reference to the same node.", clone.getNode(row, column) != chromosome.getNode(row, column)); // check that both nodes reference their own position in the grid correctly @@ -110,7 +91,7 @@ public class ChromosomeTests { assertTrue("Equivalent nodes have different functions.", clone.getNode(row, column).getFunction() == chromosome.getNode(row, column).getFunction()); // compare each connection - for (int connection = 0; connection < Parameters.getMaxArity(); connection++) { + for (int connection = 0; connection < (int) resources.get("arity"); connection++) { // first look at whether they are actually the same instance assertTrue("Nodes are connected to the same connection instance.", clone.getNode(row, column).getConnection(connection) != chromosome.getNode(row, column).getConnection(connection)); @@ -149,12 +130,12 @@ public class ChromosomeTests { clone.setInputs((Object[]) testInputs); // check that both chromosomes have the same outputs - for (int i = 0; i < Parameters.getOutputs(); i++) { + for (int i = 0; i < (int) resources.get("outputs"); i++) { assertTrue("Incorrect output returned", ((Integer) chromosome.getOutput(i).calculate()) == ((Integer) clone.getOutput(i).calculate())); } // mutate an output in clone, check that the same node in chromosome produces a different output - clone.getOutput(1).setConnection(clone.getInput(2)); + clone.getOutput(1).setConnection(resources.getRandomInt((int) resources.get("arity")), clone.getInput(2)); assertTrue("Mutation affected nodes in both chromosomes.", clone.getOutput(1).calculate() != chromosome.getOutput(1).calculate()); @@ -183,7 +164,7 @@ public class ChromosomeTests { // get random connections with the last column as reference, check that they're all within range int connectionNodes = 0, connectionOutOfRange = 0, connectionInputs = 0, connectionPicks = 100000; - int chosenColumn = Parameters.getColumns() - 1; + int chosenColumn = (int) resources.get("columns") - 1; for (int i = 0; i < connectionPicks; i++) { Connection c = chromosome.getRandomConnection(chosenColumn); if (c instanceof Node) { @@ -199,10 +180,10 @@ public class ChromosomeTests { } } - System.out.println("Out of " + connectionPicks + " connections picked from " + ((chosenColumn >= Parameters.getLevelsBack()) ? Parameters.getLevelsBack() : chosenColumn) * Parameters.getRows() + - " allowed nodes and " + Parameters.getInputs() + " inputs, " + connectionNodes + " were nodes and " + connectionInputs + " were inputs."); + System.out.println("Out of " + connectionPicks + " connections picked from " + ((chosenColumn >= (int) resources.get("levelsBack")) ? (int) resources.get("levelsBack") : chosenColumn) * (int) resources.get("rows") + + " allowed nodes and " + (int) resources.get("inputs") + " inputs, " + connectionNodes + " were nodes and " + connectionInputs + " were inputs."); - System.out.println("Node/input ratio: " + ((double) ((chosenColumn >= Parameters.getLevelsBack()) ? Parameters.getLevelsBack() : chosenColumn) * Parameters.getRows()) / (double) Parameters.getInputs() + + System.out.println("Node/input ratio: " + ((Integer) (((chosenColumn >= (int) resources.get("levelsBack")) ? (int) resources.get("levelsBack") : chosenColumn) * (Integer) resources.get("rows"))).doubleValue() / ((Integer) resources.get("inputs")).doubleValue() + ", picked ratio: " + (double) connectionNodes / (double) connectionInputs); System.out.println(connectionOutOfRange + " nodes that disrespected levels back were picked."); @@ -226,10 +207,10 @@ public class ChromosomeTests { fail("Return is neither Node nor Output."); } } - System.out.println("Out of " + mutablePicks + " mutable elements picked from " + Parameters.getNodeCount() + - " nodes and " + Parameters.getOutputs() + " outputs, " + mutableNodes + " were nodes and " + + System.out.println("Out of " + mutablePicks + " mutable elements picked from " + (int) resources.get("nodes") + + " nodes and " + (int) resources.get("outputs") + " outputs, " + mutableNodes + " were nodes and " + mutableOutputs + " were outputs."); - System.out.println("Node/output ratio: " + (double) Parameters.getNodeCount() / (double) Parameters.getOutputs() + + System.out.println("Node/output ratio: " + ((Integer) resources.get("nodes")).doubleValue() / ((Integer) resources.get("outputs")).doubleValue() + ", picked ratio: " + (double) mutableNodes / (double) mutableOutputs + "\n"); } @@ -253,12 +234,12 @@ public class ChromosomeTests { @Test public void setInputTest() { // set input values, check that acquired values are correct - Integer[] testInputs = new Integer[Parameters.getInputs()]; - for (int i = 0; i < Parameters.getInputs(); i++) { + Integer[] testInputs = new Integer[(int) resources.get("inputs")]; + for (int i = 0; i < (int) resources.get("inputs"); i++) { testInputs[i] = i * 2 - 3; } chromosome.setInputs((Object[]) testInputs); - for (int i = 0; i < Parameters.getInputs(); i++) { + for (int i = 0; i < (int) resources.get("inputs"); i++) { assertTrue("Incorrect input returned.", ((Integer) chromosome.getInput(i).getValue()) == i * 2 - 3); } } @@ -269,8 +250,8 @@ public class ChromosomeTests { @Test public void getNodeTest() { // get all nodes one by one, check that they are all correct - for (int r = 0; r < Parameters.getRows(); r++) { - for (int c = 0; c < Parameters.getColumns(); c++) { + for (int r = 0; r < (int) resources.get("rows"); r++) { + for (int c = 0; c < (int) resources.get("columns"); c++) { assertTrue("Incorrect node returned.", chromosome.getNode(r, c).getColumn() == c && chromosome.getNode(r, c).getRow() == r); } @@ -304,7 +285,7 @@ public class ChromosomeTests { assertTrue("Symmetry not obeyed.", c.compareActiveTo(chromosome)); // create a new random chromosome, this time they should not match - c = new Chromosome(); + c = new Chromosome(resources); assertTrue("Active nodes did match.", !chromosome.compareActiveTo(c)); } @@ -319,7 +300,7 @@ public class ChromosomeTests { assertTrue("Symmetry not obeyed.", c.compareTo(chromosome)); // create a new random chromosome, this time they should not match - c = new Chromosome(); + c = new Chromosome(resources); assertTrue("Chromosomes did match.", !chromosome.compareTo(c)); } /** @@ -333,20 +314,20 @@ public class ChromosomeTests { */ private Chromosome createKnownConfiguration() { // with a small topology, build a chromosome of known connections and check outputs - Parameters.setColumns(3); - Parameters.setRows(3); - Parameters.setInputs(3); - Parameters.setOutputs(2); - Parameters.setLevelsBack(3); + resources.set("columns", 3); + resources.set("rows", 3); + resources.set("inputs", 3); + resources.set("outputs", 2); + resources.set("levelsBack", 3); - Chromosome c = new Chromosome(); + Chromosome c = new Chromosome(resources); - c.getNode(0, 0).initialise(Utilities.getFunction(0), c.getInput(0), c.getInput(1)); - c.getNode(1, 1).initialise(Utilities.getFunction(0), c.getNode(0, 0), c.getInput(1)); - c.getNode(1, 2).initialise(Utilities.getFunction(0), c.getNode(1, 1), c.getInput(2)); + c.getNode(0, 0).initialise(resources.getFunction(0), c.getInput(0), c.getInput(1)); + c.getNode(1, 1).initialise(resources.getFunction(0), c.getNode(0, 0), c.getInput(1)); + c.getNode(1, 2).initialise(resources.getFunction(0), c.getNode(1, 1), c.getInput(2)); - c.getOutput(0).setConnection(c.getNode(0, 0)); - c.getOutput(1).setConnection(c.getNode(1, 2)); + c.getOutput(0).setConnection(0, c.getNode(0, 0)); + c.getOutput(1).setConnection(0, c.getNode(1, 2)); return c; } diff --git a/src/jcgp/tests/NodeTests.java b/src/jcgp/tests/NodeTests.java index 74b8140..330ef7a 100644 --- a/src/jcgp/tests/NodeTests.java +++ b/src/jcgp/tests/NodeTests.java @@ -1,13 +1,9 @@ package jcgp.tests; import static org.junit.Assert.assertTrue; - -import java.util.Random; - -import jcgp.Utilities; -import jcgp.modules.function.Arithmetic; -import jcgp.modules.function.Function; -import jcgp.parameters.Parameters; +import jcgp.CGP.Resources; +import jcgp.function.Arithmetic; +import jcgp.function.Function; import jcgp.population.Chromosome; import jcgp.population.Connection; import jcgp.population.Node; @@ -35,32 +31,22 @@ public class NodeTests { private Node node; private static Chromosome chromosome; + private static Resources resources; // these numbers will be used by the node under test private final int arg1 = 2; private final int arg2 = 5; @BeforeClass public static void setUpBeforeClass() { - // initialise utilities - Utilities.setResources(new Random(1234), null); - - // initialise parameters - Parameters.setColumns(0); - Parameters.setRows(0); - Parameters.setInputs(0); - Parameters.setOutputs(0); - Parameters.setLevelsBack(0); - Parameters.setMutationRate(10); - Parameters.setTotalGenerations(100); - Parameters.setTotalRuns(5); - Parameters.setMaxArity(2); - - chromosome = new Chromosome(); + + resources = new Resources(); + + chromosome = new Chromosome(resources); } @Before public void setUp() throws Exception { - node = new Node(chromosome, 0, 0); + node = new Node(chromosome, 0, 0, (int) resources.get("arity")); // make node with anonymous addition function and hard-coded value connections node.initialise(new Arithmetic.Addition(), new Connection[]{new Connection() { @@ -117,6 +103,12 @@ public class NodeTests { // blank return 0; } + + @Override + public String getName() { + // blank + return null; + } }; node.setFunction(f); @@ -194,7 +186,7 @@ public class NodeTests { return null; } }; - node.setConnection(conn2); + node.setConnection(resources.getRandomInt((int) resources.get("arity")), conn2); assertTrue("Connection was not found in node.", node.getConnection(0) == conn2 || node.getConnection(1) == conn2); diff --git a/src/jcgp/tests/OutputTests.java b/src/jcgp/tests/OutputTests.java index b8f7d96..00cfea3 100644 --- a/src/jcgp/tests/OutputTests.java +++ b/src/jcgp/tests/OutputTests.java @@ -2,10 +2,7 @@ package jcgp.tests; import static org.junit.Assert.assertTrue; -import java.util.Random; - -import jcgp.Utilities; -import jcgp.parameters.Parameters; +import jcgp.CGP.Resources; import jcgp.population.Chromosome; import jcgp.population.Connection; import jcgp.population.Output; @@ -30,27 +27,15 @@ public class OutputTests { private Output output; private static Chromosome chromosome; + private static Resources resources; // these are the test values private final int outputValue = 10; private final int outputIndex = 2; @BeforeClass public static void setUpBeforeClass() { - // initialise utilities - Utilities.setResources(new Random(1234), null); - - // initialise parameters - Parameters.setColumns(0); - Parameters.setRows(0); - Parameters.setInputs(0); - Parameters.setOutputs(0); - Parameters.setLevelsBack(0); - Parameters.setMutationRate(10); - Parameters.setTotalGenerations(100); - Parameters.setTotalRuns(5); - Parameters.setMaxArity(2); - - chromosome = new Chromosome(); + resources = new Resources(); + chromosome = new Chromosome(resources); } @Before @@ -61,7 +46,7 @@ public class OutputTests { @Test public void evaluationsTest() { // set source connection, check that the appropriate value is returned - output.setConnection(new Connection() { + output.setConnection(0, new Connection() { @Override public Object getValue() { @@ -96,7 +81,7 @@ public class OutputTests { return null; } }; - output.setConnection(newConn); + output.setConnection(0, newConn); assertTrue("Incorrect connection returned.", output.getSource() == newConn); } diff --git a/src/jcgp/tests/PopulationTests.java b/src/jcgp/tests/PopulationTests.java index d646b90..474b8d5 100644 --- a/src/jcgp/tests/PopulationTests.java +++ b/src/jcgp/tests/PopulationTests.java @@ -1,13 +1,7 @@ package jcgp.tests; -import static org.junit.Assert.*; - -import java.util.Random; - -import jcgp.Utilities; -import jcgp.modules.function.Arithmetic; -import jcgp.modules.function.FunctionSet; -import jcgp.parameters.Parameters; +import static org.junit.Assert.assertTrue; +import jcgp.CGP.Resources; import jcgp.population.Chromosome; import jcgp.population.Population; @@ -19,9 +13,7 @@ import org.junit.Test; * * Tests which cover the behaviour specified for a population. * - * - A population should be able to return parents and offspring separately. - * - It should be possible to iterate through all the chromosomes in a population - * with one indexing system - parents then offspring. + * - It should be possible to iterate through all the chromosomes in a population. * - When constructed with no arguments, it should generate populationSize * random chromosomes, distributed according to the EA parameters. * - If one or more chromosomes are passed into the constructor, it should use them @@ -34,81 +26,61 @@ import org.junit.Test; public class PopulationTests { private Population population; + private static Resources resources; @BeforeClass public static void setUpBeforeClass() throws Exception { - // initialise function set - FunctionSet functionSet = new FunctionSet(new Arithmetic.Addition(), new Arithmetic.Subtraction()); - - // initialise utilities - Utilities.setResources(new Random(1234), functionSet); - - // initialise parameters - Parameters.setColumns(20); - Parameters.setRows(20); - Parameters.setInputs(2); - Parameters.setOutputs(4); - Parameters.setLevelsBack(1); - Parameters.setMutationRate(10); - Parameters.setTotalGenerations(100); - Parameters.setTotalRuns(5); - Parameters.setPopulationSize(1, 4); - Parameters.setMaxArity(functionSet.getMaxArity()); + resources = new Resources(); + + +// // initialise function set +// FunctionSet functionSet = new FunctionSet(new Arithmetic.Addition(), new Arithmetic.Subtraction()); +// +// // initialise utilities +// Utilities.setResources(new Random(1234), functionSet); +// +// // initialise parameters +// Resources.setColumns(20); +// Resources.setRows(20); +// Resources.setInputs(2); +// Resources.setOutputs(4); +// Resources.setLevelsBack(1); +// Resources.setMutationRate(10); +// Resources.setTotalGenerations(100); +// Resources.setTotalRuns(5); +// Resources.setPopulationSize(1, 4); +// Resources.setMaxArity(functionSet.getMaxArity()); } @Before public void setUp() throws Exception { - population = new Population(); + population = new Population(resources); } @Test public void defaultPopulationTest() { // check that the constructor really generates populationSize chromosomes when none is given - int offspring = 0, parent = 0; - while (true) { - try { - population.getOffspring(offspring); - } catch (IndexOutOfBoundsException e) { - break; - } - offspring++; - } + int chromosomes = 0; while (true) { try { - population.getParent(parent); + population.getChromosome(chromosomes); } catch (IndexOutOfBoundsException e) { break; } - parent++; + chromosomes++; } - assertTrue("Incorrect number of chromosomes generated.", offspring + parent == Parameters.getPopulationSize()); - } - - @Test - public void offspringParentTest() { - // the first parent should not be the same as the first offspring - assertTrue("Same chromosome returned as parent and offspring", population.getOffspring(0) != population.getParent(0)); + + assertTrue("Incorrect number of chromosomes generated.", chromosomes == (int) resources.get("popSize")); } @Test - public void singleIndexTest() { - // assuming 1+4 - // the first chromosome should be the first (and only) parent - assertTrue("Incorrect chromosome returned.", population.getChromosome(0) == population.getParent(0)); - // the next 4 chromosomes should be the offspring, in order - for (int i = 0; i < Parameters.getOffspringCount(); i++) { - assertTrue("Incorrect chromosome returned.", population.getChromosome(i + 1) == population.getOffspring(i)); - } - } - - @Test public void preinitialisedChromosomeTest() { // the original chromosome that will be cloned - Chromosome oc = new Chromosome(); + Chromosome oc = new Chromosome(resources); // initialise a population with a copy of it - population = new Population(oc); + 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.getParent(0).compareTo(oc) && population.getParent(0) != oc); + assertTrue("Incorrect chromosome in population.", population.getChromosome(0).compareTo(oc) && population.getChromosome(0) != oc); } } |