diff --git a/pom.xml b/pom.xml index 52106efb..ee9496c2 100644 --- a/pom.xml +++ b/pom.xml @@ -27,27 +27,6 @@ javahelp 2.0.05 - - org.apache.commons - commons-math3 - 3.1.1 - - - commons-cli - commons-cli - 1.2 - - - com.google.code.gson - gson - 2.3 - compile - - - org.reflections - reflections - 0.9.9 - diff --git a/src/eva2/cli/Main.java b/src/eva2/cli/Main.java index b951db4b..4d2e3308 100644 --- a/src/eva2/cli/Main.java +++ b/src/eva2/cli/Main.java @@ -1,719 +1,15 @@ package eva2.cli; -import com.google.gson.*; -import eva2.OptimizerFactory; -import eva2.optimization.OptimizationStateListener; -import eva2.optimization.enums.DEType; -import eva2.optimization.enums.PSOTopology; -import eva2.optimization.population.InterfacePopulationChangedEventListener; -import eva2.optimization.modules.OptimizationParameters; -import eva2.optimization.operator.crossover.CrossoverESDefault; -import eva2.optimization.operator.crossover.InterfaceCrossover; -import eva2.optimization.operator.mutation.InterfaceMutation; -import eva2.optimization.operator.mutation.MutateDefault; -import eva2.optimization.operator.selection.InterfaceSelection; -import eva2.optimization.operator.selection.SelectXProbRouletteWheel; -import eva2.optimization.operator.terminators.CombinedTerminator; -import eva2.optimization.operator.terminators.FitnessValueTerminator; -import eva2.optimization.population.Population; -import eva2.problems.AbstractProblemDouble; -import eva2.problems.AbstractProblemDoubleOffset; -import eva2.optimization.strategies.DifferentialEvolution; -import eva2.optimization.strategies.InterfaceOptimizer; -import org.apache.commons.cli.*; -import org.reflections.Reflections; - -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; +import eva2.optimization.go.InterfaceOptimizationParameters; /** - * Main Class for the EvA2 Command Line Interface - * - * The command line interface features a limited subset of the EvA2 - * optimization suite since it's difficult to parameterize all available - * classes in EvA2 from the command line. - * Supported Features: - * - Select all optimization problems that implement InterfaceOptimizationProblem - * - Select all optimizers that implement InterfaceOptimizer - * * Not all optimizers are configurable on the command line and will run - * with default parameters. - * * Optimizers can use the @Description / @Parameter annotations to specify - * parameters for CLI - * - Configure default parameters - * * Population size - * * Number of optimization runs (multi-runs) - * - Termination: - * * Not configurable! - * * Default: EvaluationTerminator(20000) + * Created by becker on 01.11.2014. */ -public class Main implements OptimizationStateListener, InterfacePopulationChangedEventListener { - private static Logger LOGGER = Logger.getLogger(Main.class.getName()); - private int populationSize = 20; - private int numberOfRuns = 1; - private int dimension = 30; - private long seed = System.currentTimeMillis(); - - private AbstractProblemDoubleOffset problem; - private InterfaceOptimizer optimizer; - private InterfaceMutation mutator; - private InterfaceCrossover crossover; - private InterfaceSelection selection; - - private JsonObject jsonObject; - private JsonArray optimizationRuns; - private JsonArray generationsArray; - - private double[] fBias = { -4.5000000e+002, -4.5000000e+002, -4.5000000e+002, -4.5000000e+002, -3.1000000e+002, - 3.9000000e+002, -1.8000000e+002, -1.4000000e+002, -3.3000000e+002, -3.3000000e+002, 9.0000000e+001, - -4.6000000e+002, -1.3000000e+002, -3.0000000e+002, 1.2000000e+002, 1.2000000e+002, 1.2000000e+002, - 1.0000000e+001, 1.0000000e+001, 1.0000000e+001, 3.6000000e+002, 3.6000000e+002, 3.6000000e+002, - 2.6000000e+002, 2.6000000e+002}; - - /** - * Creates a set of default options used in all optimizations. - * - * @return Options Default options used for optimizations - */ - private Options createDefaultCommandLineOptions() { - Options opt = new Options(); - - opt.addOption(OptionBuilder - .withLongOpt("optimizer") - .withDescription("Optimizer") - .hasArg() - .create("op") - ); - - opt.addOption("ps", "popsize", true, "Population size"); - opt.addOption("n", "runs", true, "Number of runs to perform"); - opt.addOption("s", "seed", true, "Random seed"); - - // Those two only make sense when used in an algorithm with mutation/crossover - opt.addOption("pc", true, "Crossover Probability"); - opt.addOption("pm", true, "Mutation Probability"); - - opt.addOption("mutator", true, "Mutator Operator"); - opt.addOption("crossover", true, "Crossover Operator"); - opt.addOption("selection", true, "Selection Operator"); - - opt.addOption(OptionBuilder - .withLongOpt("help") - .withDescription("Shows this help message or specific help for [optimizer]") - .hasOptionalArgs(1) - .create('h') - ); - - opt.addOption(OptionBuilder - .withLongOpt("problem") - .withDescription("Select Optimization Problem to optimize.") - .hasArg() - .create('p') - ); - opt.addOption("dim", true, "Problem Dimension"); - return opt; - } - - - - @Override - public void performedStop() { - LOGGER.info("Optimization stopped."); - } - - @Override - public void performedStart(String infoString) { - LOGGER.info("Optimization started."); - } - - @Override - public void performedRestart(String infoString) { - LOGGER.info("Optimization restarted."); - } - - @Override - public void updateProgress(int percent, String msg) { - printProgressBar(percent); - } - - public static void printProgressBar(int percent) { - StringBuilder bar = new StringBuilder("["); - - for (int i = 0; i < 50; i++) { - if (i < (percent / 2)) { - bar.append("="); - } else if (i == (percent / 2)) { - bar.append(">"); - } else { - bar.append(" "); - } - } - - bar.append("] " + percent + "% "); - System.out.print("\r" + bar.toString()); - } - - public static Map> createOptimizerList() { - Map> optimizerList = new TreeMap<>(); - - Reflections reflections = new Reflections("eva2.optimization.strategies"); - Set> optimizers = reflections.getSubTypesOf(InterfaceOptimizer.class); - for (Class optimizer : optimizers) { - // We only want instantiable classes - if (optimizer.isInterface() || Modifier.isAbstract(optimizer.getModifiers())) { - continue; - } - optimizerList.put(optimizer.getSimpleName(), optimizer); - } - return optimizerList; - } - - public static Map> createProblemList() { - Map> problemList = new TreeMap<>(); - Reflections reflections = new Reflections("eva2.problems"); - Set> problems = reflections.getSubTypesOf(AbstractProblemDoubleOffset.class); - for (Class problem : problems) { - // We only want instantiable classes - if (problem.isInterface() || Modifier.isAbstract(problem.getModifiers())) { - continue; - } - problemList.put(problem.getSimpleName(), problem); - } - return problemList; - } - - public static Map> createMutatorList() { - Map> mutationList = new TreeMap<>(); - Reflections reflections = new Reflections("eva2.optimization.operator.mutation"); - Set> mutators = reflections.getSubTypesOf(InterfaceMutation.class); - for (Class mutator : mutators) { - // We only want instantiable classes - if (mutator.isInterface() || Modifier.isAbstract(mutator.getModifiers())) { - continue; - } - mutationList.put(mutator.getSimpleName(), mutator); - } - return mutationList; - } - - public static Map> createCrossoverList() { - Map> crossoverList = new TreeMap<>(); - Reflections reflections = new Reflections("eva2.optimization.operator.crossover"); - Set> crossovers = reflections.getSubTypesOf(InterfaceCrossover.class); - for (Class crossover : crossovers) { - // We only want instantiable classes - if (crossover.isInterface() || Modifier.isAbstract(crossover.getModifiers())) { - continue; - } - crossoverList.put(crossover.getSimpleName(), crossover); - } - return crossoverList; - } - - public static Map> createSelectionList() { - Map> selectionList = new TreeMap<>(); - Reflections reflections = new Reflections("eva2.optimization.operator.selection"); - Set> selections = reflections.getSubTypesOf(InterfaceSelection.class); - for (Class selection : selections) { - // We only want instantiable classes - if (selection.isInterface() || Modifier.isAbstract(selection.getModifiers())) { - continue; - } - selectionList.put(selection.getSimpleName(), selection); - } - return selectionList; - } - - - - public static void showHelp(Options options) { - HelpFormatter helpFormatter = new HelpFormatter(); - helpFormatter.printHelp("java -jar EvA2.jar", "Global Parameters", options, "", true); - } +public class Main { public static void main(String[] args) { - Main optimizationMain = new Main(args); - optimizationMain.runOptimization(); - } + InterfaceOptimizationParameters parameters = OptimizationBuilder.parseArguments(args); - public Main(String[] args) { - int index = Arrays.asList(args).indexOf("--"); - String[] programParams = args; - String[] optimizerParams; - - if (index >= 0) { - programParams = Arrays.copyOfRange(args, 0, index); - optimizerParams = Arrays.copyOfRange(args, index + 1, args.length); - } else { - optimizerParams = new String[]{}; - } - - this.jsonObject = new JsonObject(); - this.optimizationRuns = new JsonArray(); - this.generationsArray = new JsonArray(); - - /** - * Default command line options only require help or optimizer. - * Later we build extended command line options depending on - * the selected optimizer. - */ - Options defaultOptions = this.createDefaultCommandLineOptions(); - - /** - * Parse default options. - */ - CommandLineParser cliParser = new BasicParser(); - CommandLine commandLine = null; - try { - commandLine = cliParser.parse(defaultOptions, programParams); - } catch (ParseException e) { - showHelp(defaultOptions); - System.exit(-1); - } - - /** - * Process help and help sub pages. - */ - if (commandLine.hasOption("help")) { - String helpOption = commandLine.getOptionValue("help"); - if (helpOption == null) { - showHelp(defaultOptions); - } else { - switch (helpOption) { - case "optimizer": - showOptimizerHelp(); - break; - case "problem": - listProblems(); - break; - default: - showHelp(defaultOptions); - break; - } - } - - System.exit(0); - } - - // OK, so we've got valid parameters - let's setup the optimizer and problem - if (commandLine.hasOption("popsize")) { - this.populationSize = Integer.parseInt(commandLine.getOptionValue("popsize")); - } - - if (commandLine.hasOption("runs")) { - this.numberOfRuns = Integer.parseInt(commandLine.getOptionValue("runs")); - } - - if (commandLine.hasOption("seed")) { - this.seed = Long.parseLong(commandLine.getOptionValue("seed")); - } - - if (commandLine.hasOption("dim")) { - this.dimension = Integer.parseInt(commandLine.getOptionValue("dim")); - } - - if (commandLine.hasOption("problem")) { - String problemName = commandLine.getOptionValue("problem"); - setProblemFromName(problemName); - this.problem.setProblemDimension(this.dimension); - } else { - LOGGER.severe("No problem specified. Please specify a problem with '--problem'."); - System.exit(-1); - } - - - if (commandLine.hasOption("mutator")) { - String mutatorName = commandLine.getOptionValue("mutator"); - try { - setMutatorFromName(mutatorName); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - System.exit(-1); - } - } else { - this.mutator = new MutateDefault(); - } - - if (commandLine.hasOption("crossover")) { - String crossoverName = commandLine.getOptionValue("crossover"); - try { - setCrossoverFromName(crossoverName); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - System.exit(-1); - } - } else { - this.crossover = new CrossoverESDefault(); - } - - if (commandLine.hasOption("selection")) { - String selectionName = commandLine.getOptionValue("selection"); - try { - setSelectionFromName(selectionName); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - System.exit(-1); - } - } else { - this.selection = new SelectXProbRouletteWheel(); - } - - // Depends on mutator/crossover/selection being set - if (commandLine.hasOption("optimizer")) { - String optimizerName = commandLine.getOptionValue("optimizer"); - try { - createOptimizerFromName(optimizerName, optimizerParams); - } catch(Exception ex) { - System.out.println(ex.getMessage()); - System.exit(-1); - } - } - - this.jsonObject.addProperty("population_size", this.populationSize); - this.jsonObject.addProperty("number_of_runs", this.numberOfRuns); - this.jsonObject.addProperty("dimension", this.dimension); - this.jsonObject.addProperty("seed", this.seed); - - JsonObject problemObject = new JsonObject(); - problemObject.addProperty("name", this.problem.getName()); - problemObject.addProperty("dimension", 30); - this.jsonObject.add("problem", problemObject); - } - - private void setMutatorFromName(String mutatorName) { - Map> mutatorList = createMutatorList(); - - Class mutator = mutatorList.get(mutatorName); - try { - this.mutator = mutator.newInstance(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - private void setCrossoverFromName(String crossoverName) { - Map> crossoverList = createCrossoverList(); - - Class crossover = crossoverList.get(crossoverName); - try { - this.crossover = crossover.newInstance(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - private void setSelectionFromName(String selectionName) { - Map> selectionList = createSelectionList(); - - Class selection = selectionList.get(selectionName); - try { - this.selection = selection.newInstance(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - /** - * This method will create the various optimizers that are supported on the CLI. - * It's a really messy process since neither Java nor args4j/apache-cli can handle - * complex object parameters. The trick here is that all parameters after the - * double-dash (--) are treated as parameters for the optimization algorithm. - * - * @param optimizerName The name of the optimizer. - * @param optimizerParams The remaining command line parameters. - * @throws Exception - */ - private void createOptimizerFromName(String optimizerName, String[] optimizerParams) throws Exception { - Options opt = new Options(); - CommandLineParser cliParser = new BasicParser(); - CommandLine commandLine = null; - - switch(optimizerName) { - case "DifferentialEvolution": { - opt.addOption("F", true, "Differential Weight"); - opt.addOption("CR", true, "Crossover Rate"); - opt.addOption("DEType", true, "DE Type ()"); - /** - * Parse default options. - */ - try { - commandLine = cliParser.parse(opt, optimizerParams); - } catch (ParseException e) { - showHelp(opt); - System.exit(-1); - } - - - double f = 0.8, lambda = 0.6, cr = 0.6; - if (commandLine.hasOption("F")) { - f = Double.parseDouble(commandLine.getOptionValue("F")); - } - - if (commandLine.hasOption("CR")) { - cr = Double.parseDouble(commandLine.getOptionValue("CR")); - } - - this.optimizer = OptimizerFactory.createDifferentialEvolution(this.problem, this.populationSize, f, lambda, cr, this); - - if (commandLine.hasOption("DEType")) { - ((DifferentialEvolution)this.optimizer).setDEType( - DEType.getFromId( - Integer.parseInt(commandLine.getOptionValue("DEType")) - ) - ); - } - - break; - } - case "GeneticAlgorithm": { - double pm = 0.01, pc = 0.5; - opt.addOption("pm", true, "Mutation Probability"); - opt.addOption("pc", true, "Crossover Probability"); - - /** - * Parse default options. - */ - try { - commandLine = cliParser.parse(opt, optimizerParams); - } catch (ParseException e) { - showHelp(opt); - System.exit(-1); - } - - if (commandLine.hasOption("pm")) { - pm = Double.parseDouble(commandLine.getOptionValue("pm")); - } - - if (commandLine.hasOption("pc")) { - pc = Double.parseDouble(commandLine.getOptionValue("pc")); - } - - this.optimizer = OptimizerFactory.createGeneticAlgorithm(mutator, pm, crossover, pc, selection, this.populationSize, this.problem, this); - break; - } - case "ParticleSwarmOptimization": { - double phi1 = 2.05, phi2 = 2.05, speedLimit = 0.1; - int topoRange = 2; - PSOTopology selectedTopology = PSOTopology.star; - - opt.addOption("speedLimit", true, "Speed Limit"); - opt.addOption("topology", true, "Particle Swarm Topology (0-7)"); - opt.addOption("phi1", true, "Phi 1"); - opt.addOption("phi2", true, "Phi 2"); - - /** - * Parse default options. - */ - try { - commandLine = cliParser.parse(opt, optimizerParams); - } catch (ParseException e) { - showHelp(opt); - System.exit(-1); - } - - if (commandLine.hasOption("phi1")) { - phi1 = Double.parseDouble(commandLine.getOptionValue("phi1")); - } - - if (commandLine.hasOption("phi2")) { - phi2 = Double.parseDouble(commandLine.getOptionValue("phi2")); - } - - if (commandLine.hasOption("topology")) { - selectedTopology = PSOTopology.getFromId(Integer.parseInt(commandLine.getOptionValue("topology"))); - } - - if (commandLine.hasOption("speedLimit")) { - speedLimit = Double.parseDouble(commandLine.getOptionValue("speedLimit")); - } - - this.optimizer = OptimizerFactory.createParticleSwarmOptimization(problem, this.populationSize, phi1, phi2, speedLimit, selectedTopology, topoRange, this); - break; - } - case "EvolutionStrategies": { - double pm, pc; - int mu = 5, lambda = 20; - boolean plusStrategy = false; - - opt.addOption("pm", true, "Mutation Probability"); - opt.addOption("pc", true, "Crossover Probability"); - opt.addOption("mu", true, "Mu"); - opt.addOption("lambda", true, "Lambda"); - opt.addOption("plusStrategy", true, "Whether to use the plus or comma strategy."); - - /** - * Parse default options. - */ - try { - commandLine = cliParser.parse(opt, optimizerParams); - } catch (ParseException e) { - showHelp(opt); - System.exit(-1); - } - - if (commandLine.hasOption("pm")) { - pm = Double.parseDouble(commandLine.getOptionValue("pm")); - } else { - pm = 0.01; - } - - if (commandLine.hasOption("pc")) { - pc = Double.parseDouble(commandLine.getOptionValue("pc")); - } else { - pc = 0.9; - } - - if (commandLine.hasOption("mu")) { - mu = Integer.parseInt(commandLine.getOptionValue("mu")); - } - - if (commandLine.hasOption("lambda")) { - lambda = Integer.parseInt(commandLine.getOptionValue("lambda")); - } - - if (commandLine.hasOption("plusStrategy")) { - plusStrategy = Boolean.parseBoolean(commandLine.getOptionValue("plusStrategy")); - } - - this.optimizer = OptimizerFactory.createEvolutionStrategy(mu, lambda, plusStrategy, this.mutator, pm, this.crossover, pc, this.selection, problem, this); - break; - } - default: - throw new Exception("Unsupported Optimizer"); - } - } - - private void setProblemFromName(String problemName) { - Map> problemList = createProblemList(); - - Class problem = problemList.get(problemName); - try { - this.problem = problem.newInstance(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - //setCECDefaults(this.problem); - } - - private void setCECDefaults(AbstractProblemDouble problem) { - switch(problem.getName()) { - case "F1-Problem": // F1: Shifted Sphere - this.problem.setDefaultRange(100); - this.problem.setYOffset(fBias[0]); - break; - case "F2-Problem": // F6: Shifted Rosenbrock's Function - this.problem.setDefaultRange(100); - this.problem.setYOffset(fBias[5]); - break; - case "F5-Problem": // F2: Schwefel's 1.2 - this.problem.setDefaultRange(100); - this.problem.setYOffset(fBias[1]); - break; - case "F6-Problem": // F9: Shifted Rastrigin's Function - this.problem.setDefaultRange(5); - this.problem.setYOffset(fBias[8]); - break; - default: - LOGGER.info("No CEC'05 default parameters for this problem found."); - break; - } - } - - /** - * Executes the optimization and outputs a JSON document to the command line - * with the statistics of the optimization run(s). - */ - private void runOptimization() { - for(int i = 0; i < this.numberOfRuns; i++) { - // Terminate after 10000 function evaluations OR after reaching a fitness < 0.1 - OptimizerFactory.setEvaluationTerminator(500000); - //OptimizerFactory.setTerminator(new FitnessValueTerminator(new double[]{0.0001})); - OptimizerFactory.addTerminator(new FitnessValueTerminator(new double[]{0.0001}), CombinedTerminator.OR); - - LOGGER.log(Level.INFO, "Running {0}", optimizer.getName()); - - OptimizationParameters params = OptimizerFactory.makeParams(optimizer, this.populationSize, this.problem, this.seed, OptimizerFactory.getTerminator()); - double[] result = OptimizerFactory.optimizeToDouble(params); - - JsonObject optimizationDetails = new JsonObject(); - optimizationDetails.addProperty("total_time", 1.0); - optimizationDetails.addProperty("total_function_calls", optimizer.getPopulation().getFunctionCalls()); - optimizationDetails.addProperty("termination_criteria", OptimizerFactory.terminatedBecause()); - optimizationDetails.add("generations", this.generationsArray); - - JsonArray solutionArray = new JsonArray(); - for(double val : result) { - solutionArray.add(new JsonPrimitive(val)); - } - optimizationDetails.add("solution", solutionArray); - - this.optimizationRuns.add(optimizationDetails); - - // Needs to be re-created here. - this.generationsArray = new JsonArray(); - } - - this.jsonObject.add("runs", this.optimizationRuns); - - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - System.out.println(gson.toJson(this.jsonObject)); - } - - private static void showOptimizerHelp() { - Map> optimizerList = createOptimizerList(); - - System.out.println("Available Optimizers:"); - for (String name : optimizerList.keySet()) { - System.out.printf("%s\n", name); - } - } - - private static void listProblems() { - Map> problemList = createProblemList(); - - System.out.println("Available Problems:"); - for (String name : problemList.keySet()) { - System.out.printf("%s\n", name); - } - } - - @Override - public void registerPopulationStateChanged(Object source, String name) { - if (name.equals("NextGenerationPerformed")) { - InterfaceOptimizer optimizer = (InterfaceOptimizer)source; - Population population = optimizer.getPopulation(); - - JsonObject newGeneration = new JsonObject(); - newGeneration.addProperty("generation", population.getGeneration()); - newGeneration.addProperty("function_calls", population.getFunctionCalls()); - - JsonArray bestFitness = new JsonArray(); - for(double val : population.getBestFitness()) { - bestFitness.add(new JsonPrimitive(val)); - } - newGeneration.add("best_fitness", bestFitness); - - JsonArray meanFitness = new JsonArray(); - for(double val : population.getMeanFitness()) { - meanFitness.add(new JsonPrimitive(val)); - } - newGeneration.add("mean_fitness", meanFitness); - //System.out.println(newGeneration.toString()); - this.generationsArray.add(newGeneration); - } } } diff --git a/src/eva2/cli/OptimizationBuilder.java b/src/eva2/cli/OptimizationBuilder.java new file mode 100644 index 00000000..9cf317d1 --- /dev/null +++ b/src/eva2/cli/OptimizationBuilder.java @@ -0,0 +1,208 @@ +package eva2.cli; + +import eva2.gui.BeanInspector; +import eva2.optimization.go.InterfaceOptimizationParameters; +import eva2.optimization.modules.OptimizationParameters; +import eva2.tools.ReflectPackage; +import eva2.util.annotation.Hidden; +import eva2.util.annotation.Parameter; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedHashMap; + +class ArgumentTree extends LinkedHashMap { + private Object value; + + public void setValue(Object value) { + this.value = value; + } + + public Object getValue() { + return this.value; + } + + @Override + public String toString() { + return ((value != null) ? value.toString() + ", " : "") + super.toString(); + } + + /** + * If there are no key, value pairs present and the value is unset, + * this tree belongs to a flag. + * + * @return + */ + public boolean isFlag() { + return this.size() == 0 && this.value == null; + } +} +/** + * + */ +public final class OptimizationBuilder { + private OptimizationBuilder() {} + + public static InterfaceOptimizationParameters parseArguments(String[] args) { + HashMap argumentMap = new HashMap<>(args.length/2); + int i = 0; + while (i < args.length) { + // Is it a parameter? + if (args[i].startsWith("--")) { + String key = args[i].substring(2); + String value = null; + // Is the next a value? + if (i < args.length - 1 && !args[i+1].startsWith("--")) { + value = args[i + 1]; + argumentMap.put(key, value); + i = i + 2; + } else { + argumentMap.put(key, null); + i++; + } + } + } + System.out.println(argumentMap.toString()); + ArgumentTree argumentTree = new ArgumentTree(); + for (String key : argumentMap.keySet()) { + insertIntoArgumentTree(argumentTree, key, argumentMap.get(key)); + } + System.out.println(argumentTree.toString()); + + return constructFromArgumentTree(OptimizationParameters.class, argumentTree); + } + + private static void insertIntoArgumentTree(ArgumentTree tree, String key, String value) { + // Basic type? + if (!key.contains("-")) { + if (!tree.containsKey(key)) { + tree.put(key, new ArgumentTree()); + } + ((ArgumentTree)tree.get(key)).setValue(value); + } else { + String baseKey = key.substring(0, key.indexOf('-')); + String restKey = key.substring(key.indexOf('-') + 1); + if (!tree.containsKey(baseKey)) { + tree.put(baseKey, new ArgumentTree()); + } + insertIntoArgumentTree((ArgumentTree)tree.get(baseKey), restKey, value); + } + } + + /** + * + * @param clazz + * @param tree Tree containing key, value pairs + */ + private static T constructFromArgumentTree(Class clazz, ArgumentTree tree) { + T instance = null; + + // Create new instance + if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { + // Find subclasses of clazz that match tree.getValue() + } else { + Class[] params = new Class[0]; + try { + Constructor constructor = clazz.getConstructor(params); + instance = (T)constructor.newInstance(new Object[]{}); + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + + /* No need to continue if there are no parameters to set */ + if (tree.isEmpty()) { + return instance; + } + + BeanInfo info; + try { + if (clazz.isInterface()) { + info = Introspector.getBeanInfo(clazz); + } else { + info = Introspector.getBeanInfo(clazz, Object.class); + } + PropertyDescriptor[] properties = info.getPropertyDescriptors(); + int foundParameters = 0; + for (PropertyDescriptor pd : properties) { + String name = pd.getName(); + Method getter = pd.getReadMethod(); + Method setter = pd.getWriteMethod(); + Class type = pd.getPropertyType(); + // We skip non-existing setters or setters that are hidden by annotation + if (setter == null || setter.isAnnotationPresent(Hidden.class)) { + continue; + } + System.out.println(name + " = " + " type = " + type); + + // We use the name of the descriptor or if possible + // one that is given by the @Parameter annotation. + if (setter.isAnnotationPresent(Parameter.class)) { + Parameter param = setter.getAnnotation(Parameter.class); + if (!param.name().isEmpty()) { + name = param.name(); + } + } + + /** + * If the tree contains this property we try to set it on the object. + */ + if (tree.containsKey(name)) { + foundParameters++; + Object obj; + if (type.isPrimitive() && ((ArgumentTree)tree.get(name)).getValue() != null) { + obj = BeanInspector.stringToPrimitive((String)((ArgumentTree) tree.get(name)).getValue(), type); + } else { + // The subtree has the name of the class + String className = (String)((ArgumentTree)tree.get(name)).getValue(); + + Class subType; + if (className != null) { + // Try to get the actual class from its name + subType = getClassFromName(className, type); + } else { + subType = type; + } + + // Here the recursion starts + obj = constructFromArgumentTree(subType, (ArgumentTree) tree.get(name)); + } + + // We preserve the default if obj is null + if (obj != null) { + BeanInspector.callIfAvailable(instance, setter.getName(), new Object[]{obj}); + } + } + + // If we configured all parameters in the tree we can break the loop + if (tree.size() == foundParameters) { + break; + } + } + } catch (IntrospectionException ex) { + ex.printStackTrace(); + } + + return instance; + } + + private static Class getClassFromName(String name, Class type) { + Class[] classes = ReflectPackage.getAssignableClassesInPackage("eva2", type, true, true); + for (Class clazz : classes) { + // We allow both the fully qualified name (eva2.optimization.strategies.GeneticAlgorithm + // and the simple name (GeneticAlgorithm) + if (clazz.getName().equals(name) || clazz.getSimpleName().equals(name)) { + return clazz; + } + } + + return null; + } +} diff --git a/src/eva2/optimization/individuals/ESIndividualDoubleData.java b/src/eva2/optimization/individuals/ESIndividualDoubleData.java index 854995f6..c1d7aa3e 100644 --- a/src/eva2/optimization/individuals/ESIndividualDoubleData.java +++ b/src/eva2/optimization/individuals/ESIndividualDoubleData.java @@ -105,9 +105,6 @@ public class ESIndividualDoubleData extends AbstractEAIndividual implements Inte } } -/************************************************************************************ - * InterfaceDataTypeDouble methods - */ /** * This method allows you to request a certain amount of double data * @@ -235,9 +232,6 @@ public class ESIndividualDoubleData extends AbstractEAIndividual implements Inte System.arraycopy(doubleData, 0, this.genotype, 0, doubleData.length); } -/************************************************************************************ - * AbstractEAIndividual methods - */ /** * This method will allow a default initialisation of the individual * @@ -304,9 +298,6 @@ public class ESIndividualDoubleData extends AbstractEAIndividual implements Inte return strB.toString(); } -/************************************************************************************ - * InterfaceESIndividual methods - */ /** * This method will allow the user to read the ES 'genotype' * diff --git a/src/eva2/optimization/individuals/GAIndividualDoubleData.java b/src/eva2/optimization/individuals/GAIndividualDoubleData.java index 21770a5d..20682a31 100644 --- a/src/eva2/optimization/individuals/GAIndividualDoubleData.java +++ b/src/eva2/optimization/individuals/GAIndividualDoubleData.java @@ -10,6 +10,7 @@ import eva2.optimization.operator.mutation.MutateGAUniform; import eva2.problems.InterfaceOptimizationProblem; import eva2.tools.math.RNG; import eva2.util.annotation.Description; +import eva2.util.annotation.Parameter; import java.util.BitSet; @@ -229,10 +230,6 @@ public class GAIndividualDoubleData extends AbstractEAIndividual implements Inte } } -/************************************************************************************ - * AbstractEAIndividual methods - */ - /** * This method will initialize the individual with a given value for the * phenotype. @@ -373,6 +370,7 @@ public class GAIndividualDoubleData extends AbstractEAIndividual implements Inte * * @param coding The used genotype coding method */ + @Parameter(name = "coding", description = "Choose the coding to use.") public void setGACoding(InterfaceGADoubleCoding coding) { this.doubleCoding = coding; } @@ -381,16 +379,13 @@ public class GAIndividualDoubleData extends AbstractEAIndividual implements Inte return this.doubleCoding; } - public String gADoubleCodingTipText() { - return "Choose the coding to use."; - } - /** * This method allows you to set the number of mulitruns that are to be performed, * necessary for stochastic optimizers to ensure reliable results. * * @param precision The number of multiruns that are to be performed */ + @Parameter(description = "Gives the number of bits to be used to code a double.") public void setPrecision(int precision) { this.precision = precision; } @@ -398,8 +393,4 @@ public class GAIndividualDoubleData extends AbstractEAIndividual implements Inte public int getPrecision() { return this.precision; } - - public String precisionTipText() { - return "Gives the number of bits to be used to code a double."; - } } diff --git a/src/eva2/optimization/population/Population.java b/src/eva2/optimization/population/Population.java index 32f537ec..97462e20 100644 --- a/src/eva2/optimization/population/Population.java +++ b/src/eva2/optimization/population/Population.java @@ -15,6 +15,7 @@ import eva2.tools.math.Mathematics; import eva2.tools.math.RNG; import eva2.tools.math.StatisticUtils; import eva2.util.annotation.Description; +import eva2.util.annotation.Hidden; import eva2.util.annotation.Parameter; import java.util.*; @@ -602,6 +603,7 @@ public class Population extends ArrayList implements PopulationInterface, Clonea return historyList; } + @Hidden public void setHistory(LinkedList theHist) { historyList = theHist; } @@ -684,6 +686,7 @@ public class Population extends ArrayList implements PopulationInterface, Clonea * * @param d The new number of functioncalls. */ + @Hidden public void setFunctionCalls(int d) { this.functionCallCount = d; } @@ -738,6 +741,7 @@ public class Population extends ArrayList implements PopulationInterface, Clonea * * @param gen the value to set as new generation index */ + @Hidden public void setGeneration(int gen) { this.generationCount = gen; } @@ -2519,11 +2523,8 @@ public class Population extends ArrayList implements PopulationInterface, Clonea return seedCardinality; } + @Parameter(description = "The initial cardinality for binary genotype individuals, given as pair of mean and std.dev.") public void setSeedCardinality(Pair seedCardinality) { this.seedCardinality = seedCardinality; } - - public String seedCardinalityTipText() { - return "The initial cardinality for binary genotype individuals, given as pair of mean and std.dev."; - } } diff --git a/src/eva2/optimization/statistics/EvAStatisticalEvaluation.java b/src/eva2/optimization/statistics/EvAStatisticalEvaluation.java index 5e6d9fbb..7db8162f 100644 --- a/src/eva2/optimization/statistics/EvAStatisticalEvaluation.java +++ b/src/eva2/optimization/statistics/EvAStatisticalEvaluation.java @@ -6,11 +6,12 @@ import eva2.optimization.enums.StatisticsOnTwoSampledData; import eva2.tools.ReflectPackage; import eva2.tools.StringTools; import eva2.tools.math.Mathematics; -import org.apache.commons.math3.stat.ranking.NaNStrategy; -import org.apache.commons.math3.stat.ranking.TiesStrategy; import java.text.DecimalFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.logging.Logger; /** @@ -257,19 +258,15 @@ public class EvAStatisticalEvaluation { private static String calculateMannWhitney(String field, OptimizationJob job1, OptimizationJob job2) { double[] dat1 = job1.getDoubleDataColumn(field); double[] dat2 = job2.getDoubleDataColumn(field); - org.apache.commons.math3.stat.inference.MannWhitneyUTest mU = new org.apache.commons.math3.stat.inference.MannWhitneyUTest(NaNStrategy.FAILED, TiesStrategy.AVERAGE); double t = Double.NaN; if (dat1 != null && dat2 != null) { - - return "" + mU.mannWhitneyUTest(dat1, dat2); - /* Object obj = ReflectPackage.instantiateWithParams("jsc.independentsamples.MannWhitneyTest", new Object[]{dat1, dat2}, null); if (obj != null) { Object sp = BeanInspector.callIfAvailable(obj, "getSP", new Object[]{}); t = (Double) sp; } else { LOGGER.warning("For the MannWhitney test, the JSC package is required on the class path!"); - }*/ + } } return "" + t; } diff --git a/src/eva2/optimization/strategies/MultiObjectiveCMAES.java b/src/eva2/optimization/strategies/MultiObjectiveCMAES.java index b5ab0ad2..38a42de8 100644 --- a/src/eva2/optimization/strategies/MultiObjectiveCMAES.java +++ b/src/eva2/optimization/strategies/MultiObjectiveCMAES.java @@ -100,7 +100,7 @@ public class MultiObjectiveCMAES extends AbstractOptimizer implements Serializab /* * (non-Javadoc) * - * @see eva2.optimization.strategies.InterfaceOptimizer#init() + * @see eva2.optimization.strategies.InterfaceOptimizer#initialize() */ @Override public void initialize() { diff --git a/src/eva2/problems/AbstractOptimizationProblem.java b/src/eva2/problems/AbstractOptimizationProblem.java index 17943a1f..bf1052d6 100644 --- a/src/eva2/problems/AbstractOptimizationProblem.java +++ b/src/eva2/problems/AbstractOptimizationProblem.java @@ -87,13 +87,11 @@ public abstract class AbstractOptimizationProblem implements InterfaceOptimizati return parallelThreads; } + @Parameter(name = "parallel", description = "Set the number of threaded parallel function evaluations - interesting for slow functions and generational optimizers.") public void setParallelThreads(int parallelThreads) { this.parallelThreads = parallelThreads; } - public String parallelThreadsTipText() { - return "Set the number of threaded parallel function evaluations - interesting for slow functions and generational optimizers."; - } /** * This method initializes the problem instance. @@ -406,10 +404,6 @@ public abstract class AbstractOptimizationProblem implements InterfaceOptimizati return template; } - public String individualTemplateTipText() { - return "Choose the individual representation to use."; - } - /** * This method extracts the individuals from a given population that are assumed to correspond to local or global optima. * Similar individuals are clustered together with a density based clustering method diff --git a/src/eva2/problems/AbstractProblemDouble.java b/src/eva2/problems/AbstractProblemDouble.java index 74cf3d59..6bca47d6 100644 --- a/src/eva2/problems/AbstractProblemDouble.java +++ b/src/eva2/problems/AbstractProblemDouble.java @@ -22,6 +22,7 @@ import eva2.tools.diagram.ColorBarCalculator; import eva2.tools.math.Jama.Matrix; import eva2.tools.math.Mathematics; import eva2.tools.math.RNG; +import eva2.util.annotation.Parameter; /** * For a double valued problem, there are two main methods to implement: @@ -285,6 +286,7 @@ public abstract class AbstractProblemDouble extends AbstractOptimizationProblem * * @param noise The sigma for a gaussian random number. */ + @Parameter(description = "Gaussian noise level on the fitness value.") public void setNoise(double noise) { if (noise < 0) { noise = 0; @@ -301,15 +303,12 @@ public abstract class AbstractProblemDouble extends AbstractOptimizationProblem return this.noise; } - public String noiseTipText() { - return "Gaussian noise level on the fitness value."; - } - /** * This method allows you to choose the EA individual used by the problem. * * @param indy The EAIndividual type */ + @Parameter(name = "individual", description = "Base individual type defining the data representation and mutation/crossover operators") public void setEAIndividual(InterfaceDataTypeDouble indy) { this.template = (AbstractEAIndividual) indy; } @@ -324,10 +323,6 @@ public abstract class AbstractProblemDouble extends AbstractOptimizationProblem return (InterfaceDataTypeDouble) this.template; } - public String EAIndividualTipText() { - return "Set the base individual type defining the data representation and mutation/crossover operators"; - } - /** * A (symmetric) absolute range limit. * @@ -351,6 +346,7 @@ public abstract class AbstractProblemDouble extends AbstractOptimizationProblem return "Absolute limit for the symmetric range in any dimension"; } + @Parameter(name = "rotate", description = "If marked, the function is rotated by 22.5 degrees along every axis.") public void setDoRotation(boolean doRotation) { this.doRotation = doRotation; if (!doRotation) { @@ -362,10 +358,6 @@ public abstract class AbstractProblemDouble extends AbstractOptimizationProblem return doRotation; } - public String doRotationTipText() { - return "If marked, the function is rotated by 22.5 degrees along every axis."; - } - /** * ******************************************************************************************************************* * These are for InterfaceParamControllable diff --git a/src/eva2/problems/SimpleProblemWrapper.java b/src/eva2/problems/SimpleProblemWrapper.java index dc0623ab..494f65d9 100644 --- a/src/eva2/problems/SimpleProblemWrapper.java +++ b/src/eva2/problems/SimpleProblemWrapper.java @@ -12,6 +12,7 @@ import eva2.problems.simple.InterfaceSimpleProblem; import eva2.problems.simple.SimpleF1; import eva2.problems.simple.SimpleProblemBinary; import eva2.problems.simple.SimpleProblemDouble; +import eva2.util.annotation.Parameter; import java.util.BitSet; @@ -164,6 +165,7 @@ public class SimpleProblemWrapper extends AbstractOptimizationProblem { /** * @param simProb the simProb to set */ + @Parameter(description = "Set the simple problem class which is to be optimized") public void setSimpleProblem(InterfaceSimpleProblem simProb) { this.simProb = simProb; initTemplate(); @@ -186,19 +188,13 @@ public class SimpleProblemWrapper extends AbstractOptimizationProblem { } } - /** - * - */ - public String simpleProblemTipText() { - return "Set the simple problem class which is to be optimized"; - } - /** * This method allows you to choose how much noise is to be added to the * fitness. This can be used to make the optimization problem more difficult. * * @param noise The sigma for a gaussian random number. */ + @Parameter(description = "Gaussian noise level on the fitness value.") public void setNoise(double noise) { if (noise < 0) { noise = 0; @@ -210,11 +206,6 @@ public class SimpleProblemWrapper extends AbstractOptimizationProblem { return this.noise; } - public String noiseTipText() { - return "Gaussian noise level on the fitness value."; - } - - /** * A (symmetric) absolute range limit. * @@ -229,15 +220,12 @@ public class SimpleProblemWrapper extends AbstractOptimizationProblem { * * @param defaultRange */ + @Parameter(name = "range", description = "Absolute limit for the symmetric range in any dimension") public void setDefaultRange(double defaultRange) { this.defaultRange = defaultRange; initTemplate(); } - public String defaultRangeTipText() { - return "Absolute limit for the symmetric range in any dimension"; - } - /** * Take care that all properties which may be hidden (and currently are) send a "hide" message to the Java Bean properties. * This is called by PropertySheetPanel in use with the GenericObjectEditor. @@ -246,16 +234,12 @@ public class SimpleProblemWrapper extends AbstractOptimizationProblem { setSimpleProblem(getSimpleProblem()); } + @Parameter(name = "individual", description = "Set the individual properties for the optimization") public void setIndividualTemplate(AbstractEAIndividual indy) { resetTemplate = false; template = indy; } - @Override - public String individualTemplateTipText() { - return "Set the individual properties for the optimization"; - } - /** * This method returns a string describing the optimization problem. * diff --git a/src/eva2/tools/Serializer.java b/src/eva2/tools/Serializer.java index e64e5d5b..f483a3f6 100644 --- a/src/eva2/tools/Serializer.java +++ b/src/eva2/tools/Serializer.java @@ -1,7 +1,5 @@ package eva2.tools; -import com.google.gson.Gson; - import java.io.*; import java.util.logging.Level; import java.util.logging.Logger;