package jcgp; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Random; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import jcgp.backend.function.Arithmetic; import jcgp.backend.function.BitwiseLogic; import jcgp.backend.function.BooleanLogic; import jcgp.backend.function.Function; import jcgp.backend.function.FunctionSet; import jcgp.backend.modules.ea.EvolutionaryAlgorithm; import jcgp.backend.modules.ea.MuPlusLambda; import jcgp.backend.modules.ea.TournamentSelection; import jcgp.backend.modules.fitness.FitnessFunction; import jcgp.backend.modules.fitness.TestCase; import jcgp.backend.modules.fitness.TestCaseEvaluator; import jcgp.backend.modules.mutator.Mutator; import jcgp.backend.modules.mutator.PointMutator; import jcgp.backend.parameters.BooleanParameter; import jcgp.backend.parameters.DoubleParameter; import jcgp.backend.parameters.IntegerParameter; import jcgp.backend.parameters.Parameter; import jcgp.backend.parameters.ParameterStatus; import jcgp.backend.population.Population; import jcgp.gui.console.Console; /** * @author Eduardo Pedroni * */ public class JCGP { /** * * The resources class encapsulates all of the resources based on which the program operates. * Each instance of JCGP contains a single instance of Resources, which gets passed to the selected * modules as the program executes. * * @author Eduardo Pedroni * */ public static class Resources { private HashMap parameters = new HashMap(); private Random numberGenerator; private TestCase[] testCases; // function sets private FunctionSet[] functionSets = new FunctionSet[] { new Arithmetic(), new BitwiseLogic(), new BooleanLogic() }; private FunctionSet functionSet = functionSets[0]; // GUI console private Console console; public Resources() { createBaseParameters(); numberGenerator = new Random(getInt("seed")); set("arity", functionSet.getMaxArity()); } public int getInt(String key) { if (parameters.get(key) instanceof IntegerParameter) { return ((IntegerParameter) parameters.get(key)).get(); } else if (parameters.get(key) instanceof DoubleParameter) { return (int) ((DoubleParameter) parameters.get(key)).get(); } else { throw new ClassCastException("Could not cast " + parameters.get(key).getClass() + " to int."); } } public double getDouble(String key) { if (parameters.get(key) instanceof IntegerParameter) { return (double) ((IntegerParameter) parameters.get(key)).get(); } else if (parameters.get(key) instanceof DoubleParameter) { return ((DoubleParameter) parameters.get(key)).get(); } else { throw new ClassCastException("Could not cast " + parameters.get(key).getClass() + " to double."); } } public boolean getBoolean(String key) { if (parameters.get(key) instanceof BooleanParameter) { return ((BooleanParameter) parameters.get(key)).get(); } else { throw new ClassCastException("Could not cast " + parameters.get(key).getClass() + " to int."); } } public void set(String key, Object value) { if (parameters.get(key) instanceof IntegerParameter) { ((IntegerParameter) parameters.get(key)).set(((Integer) value).intValue()); } else if (parameters.get(key) instanceof DoubleParameter) { ((DoubleParameter) parameters.get(key)).set(((Double) value).doubleValue()); } else if (parameters.get(key) instanceof BooleanParameter) { ((BooleanParameter) parameters.get(key)).set(((Boolean) value).booleanValue()); } } public Parameter getParameter(String key) { return parameters.get(key); } public boolean contains(String key) { return parameters.containsKey(key); } private void createBaseParameters() { parameters.put("rows", new IntegerParameter(8, "Rows", false, true) { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Chromosome must have at least 1 row."); } else { status = ParameterStatus.VALID; } } }); parameters.put("columns", new IntegerParameter(9, "Columns", false, true) { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Chromosome must have at least 1 column."); } else { status = ParameterStatus.VALID; } } }); parameters.put("inputs", new IntegerParameter(3, "Inputs", false, true) { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Chromosome must have at least 1 input."); } else { status = ParameterStatus.VALID; } } }); parameters.put("outputs", new IntegerParameter(3, "Outputs", false, true) { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Chromosome must have at least 1 output."); } else { status = ParameterStatus.VALID; } } }); parameters.put("popSize", new IntegerParameter(5, "Population", false, true) { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Population size must be at least 1."); } else { status = ParameterStatus.VALID; } } }); parameters.put("levelsBack", new IntegerParameter(2, "Levels back", false, true) { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Levels back must be at least 1."); } else if (newValue > getInt("columns")) { status = ParameterStatus.INVALID; status.setDetails("Levels back must be less than or equal to the number of columns."); } else { status = ParameterStatus.VALID; } } }); IntegerParameter nodes = new IntegerParameter(1, "Nodes", true, false) { @Override public void validate(int newValue) { // blank } }; nodes.valueProperty().bind(((SimpleIntegerProperty) ((IntegerParameter) parameters.get("rows")).valueProperty()).multiply((SimpleIntegerProperty) ((IntegerParameter) parameters.get("columns")).valueProperty())); parameters.put("nodes", nodes); parameters.put("generations", new IntegerParameter(1000000, "Generations") { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Number of generations must be greater than 0."); } else if (newValue < getInt("currentGen")) { status = ParameterStatus.WARNING_RESET; status.setDetails("Setting generations to less than the current generation will cause the experiment to restart."); } else { status = ParameterStatus.VALID; } } }); parameters.put("currentGen", new IntegerParameter(1, "Generation", true, false) { @Override public void validate(int newValue) { // blank } }); parameters.put("runs", new IntegerParameter(5, "Runs") { @Override public void validate(int newValue) { if (newValue <= 0) { status = ParameterStatus.INVALID; status.setDetails("Number of runs must be greater than 0."); } else if (newValue < getInt("currentRun")) { status = ParameterStatus.WARNING_RESET; status.setDetails("Setting runs to less than the current run will cause the experiment to restart."); } else { status = ParameterStatus.VALID; } } }); parameters.put("currentRun", new IntegerParameter(1, "Run", true, false) { @Override public void validate(int newValue) { // blank } }); parameters.put("arity", new IntegerParameter(0, "Max arity", true, false) { @Override public void validate(int newValue) { // blank } }); parameters.put("maxFitness", new IntegerParameter(3, "Max fitness", true, true) { @Override public void validate(int newValue) { // blank } }); IntegerParameter seed = new IntegerParameter(123, "Seed", false, true) { @Override public void validate(int newValue) { status = ParameterStatus.VALID; } }; seed.valueProperty().addListener(new ChangeListener() { @Override public void changed( ObservableValue observable, Number oldValue, Number newValue) { numberGenerator.setSeed(newValue.longValue()); } }); parameters.put("seed", seed); parameters.put("report", new IntegerParameter(1, "Report", false, false) { @Override public void validate(int newValue) { if (newValue > getInt("generations")) { status = ParameterStatus.WARNING; status.setDetails("No reports will be printed."); } else { status = ParameterStatus.VALID; } } }); } /** * * * @return the iterator for the set of base parameters */ public Iterator> iterator() { return parameters.entrySet().iterator(); } /* * 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(); } /* * FunctionSet functions */ public Function getRandomFunction() { Function f = functionSet.getAllowedFunction(numberGenerator.nextInt(functionSet.getAllowedFunctionCount())); return f; } public Function getFunction(int index) { return functionSet.getAllowedFunction(index); } public void setFunctionSet(int index) { functionSet = functionSets[index]; } /** * @return the functionSets */ public FunctionSet[] getFunctionSets() { return functionSets; } /** * @return the functionSet */ public FunctionSet getFunctionSet() { return functionSet; } /* * Test cases */ public void setTestCases(TestCase ... testCases) { this.testCases = testCases; } public TestCase getTestCase(int index) { return testCases[index]; } public int getTestCaseCount() { return testCases.length; } /* * Console functionality */ public void setConsole(Console console) { this.console = console; } public void println(String s) { System.out.println(s); if (console != null) { console.println(s); } } public void print(String s) { System.out.print(s); if (console != null) { console.print(s); } } } private final 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() }; private Mutator mutator; // evolutionary algorithms private EvolutionaryAlgorithm[] evolutionaryAlgorithms = new EvolutionaryAlgorithm[] { new MuPlusLambda(resources), new TournamentSelection()}; private EvolutionaryAlgorithm evolutionaryAlgorithm; // fitness evaluators private FitnessFunction[] fitnessFunctions = new FitnessFunction[] { new TestCaseEvaluator() }; private FitnessFunction fitnessFunction; /* * the population of chromosomes */ private Population population; private boolean finished = false; public JCGP() { population = new Population(resources); evolutionaryAlgorithm = evolutionaryAlgorithms[0]; mutator = mutators[0]; fitnessFunction = fitnessFunctions[0]; resources.setTestCases(new TestCase(new Integer[]{1, 2, 3}, new Integer[]{-4, 5, 6})); } public Resources getResources() { return resources; } public Population getPopulation() { return population; } /** * @return the mutators */ public Mutator[] getMutators() { return mutators; } /** * @return the mutator */ public Mutator getMutator() { return mutator; } /** * @return the evolutionaryAlgorithms */ public EvolutionaryAlgorithm[] getEvolutionaryAlgorithms() { return evolutionaryAlgorithms; } /** * @return the evolutionaryAlgorithm */ public EvolutionaryAlgorithm getEvolutionaryAlgorithm() { return evolutionaryAlgorithm; } /** * @return the fitnessFunctions */ public FitnessFunction[] getFitnessFunctions() { return fitnessFunctions; } /** * @return the fitnessFunction */ public FitnessFunction getFitnessFunction() { return fitnessFunction; } /** * @param mutator the mutator to set */ public void setMutator(int index) { this.mutator = mutators[index]; } /** * @param evolutionaryAlgorithm the evolutionaryAlgorithm to set */ public void setEvolutionaryAlgorithm(int index) { this.evolutionaryAlgorithm = evolutionaryAlgorithms[index]; } /** * @param fitnessFunction the fitnessFunction to set */ public void setFitnessFunction(int index) { this.fitnessFunction = fitnessFunctions[index]; } public void nextGeneration() { if (!finished) { fitnessFunction.evaluate(population, resources); report(); if (resources.getInt("currentGen") < resources.getInt("generations")) { // we still have generations left to go if (population.getChromosome(evolutionaryAlgorithm.getFittestChromosome()).getFitness() >= resources.getInt("maxFitness")) { // solution has been found, start next run resources.println("Solution found in generation " + resources.getInt("currentGen") + ", chromosome: " + evolutionaryAlgorithm.getFittestChromosome()); if (resources.getInt("currentRun") < resources.getInt("runs")) { // there are still runs left resources.set("currentRun", resources.getInt("currentRun") + 1); resources.set("currentGen", 0); // start a new population population = new Population(resources); } else { // no more generations and no more runs, we're done finished = true; } } else { resources.set("currentGen", resources.getInt("currentGen") + 1); } } else { // the run has ended, check if any more runs must be done resources.println("Solution not found, highest fitness achieved was " + population.getChromosome(evolutionaryAlgorithm.getFittestChromosome()).getFitness() + " by chromosome " + evolutionaryAlgorithm.getFittestChromosome()); if (resources.getInt("currentRun") < resources.getInt("runs")) { // the run has ended but there are still runs left resources.set("currentRun", resources.getInt("currentRun") + 1); resources.set("currentGen", 0); // start a new population population = new Population(resources); } else { // no more generations and no more runs, we're done finished = true; } } } evolutionaryAlgorithm.evolve(population, mutator, resources); } private void report() { if (resources.getInt("report") > 0) { if (resources.getInt("currentGen") % resources.getInt("report") == 0) { resources.println("Generation: " + resources.getInt("currentGen") + ", fitness: " + population.getChromosome(evolutionaryAlgorithm.getFittestChromosome()).getFitness()); } } } public void start() { if (!finished) { while (resources.getInt("currentGen") <= resources.getInt("generations")) { nextGeneration(); if (finished) { break; } } } } public void reset() { finished = false; population = new Population(resources); resources.set("currentGen", 1); resources.set("currentRun", 1); resources.println("-----------------------------"); resources.println("New experiment"); } public boolean isFinished() { return finished; } }