Examples
The following examples demonstrate the basic functionality provided by the MOEA Framework. Links to the full source code are provided alongside each code snippet. You may also find these and more examples in the demo application on the downloads page.
- Setup
- Example 1: Simple Run
- Example 2: Statistical Comparison of Algorithms
- Example 3: Collecting Runtime Dynamics
- Example 4: Defining New Problems
- Example 5: Defining Problems in C/C++
Setup
In order to run these examples or use the MOEA Framework, Java 6 (or a later version) must be installed on your computer. The Java 6 development kit (JDK) for Windows and Linux can be downloaded here.
To run these examples, first download and extract the latest compiled binaries from the downloads page. Windows users may extract the downloaded file using 7-zip. The files will extract to a folder called MOEAFramework-2.5. This folder will look similar to:
- MOEAFramework-2.5/
-
- examples/
- javadoc/
- lib/
- licenses/
- pf/
- global.properties
- HELP
- launch-diagnostic-tool.bat
- LICENSE
- NEWS
- README
All of the examples below are in the examples/ folder. You may compile and run an example using the following commands. Run these commands in the Command Prompt from the MOEAFramework-2.5 folder.
javac -cp "examples;lib/*" examples/Example1.java java -cp "examples;lib/*" Example1
If you receive the message 'javac' is not recognized as an internal or external command, operable program or batch file, try the following steps to setup your environment on Windows or Linux. Unix/Linux users should replace the semicolons (;) with colons (:).
Example 1: Simple Run
The Executor provides all the necessary features to execute an algorithm on a specific problem. For example, the following runs NSGA-II on the UF1 test problem. The executor provides many useful functions, such as enabling distributed evaluations across multiple cores or computers, checkpoints, instrumentation, etc.
NondominatedPopulation result = new Executor() .withProblem("UF1") .withAlgorithm("NSGAII") .withMaxEvaluations(10000) .distributeOnAllCores() .run();
Since UF1 is a bi-objective problem, printing the results will list the two objective function values for each non-dominated (Pareto optimal) solution found by NSGA-II:
Objective1 Objective2 0.9465 0.0466 0.1355 2.0513 0.1403 1.9960 0.2520 0.5120 ...
Example 2: Statistical Comparison of Algorithms
Statistical analyses are provided by the Analyzer. The Analyzer can display the min, median, max and aggregate values for multiple performance indicators, including hypervolume, generational distance, inverted generational distance, additive ε-indicator, spacing and contribution. Additionally, Kruskal-Wallis and Mann-Whitney U tests provide statistical significance results. In the example below, we compare the algorithms using the hypervolume metric.
String problem = "UF1"; String[] algorithms = { "NSGAII", "GDE3", "eMOEA" }; //setup the experiment Executor executor = new Executor() .withProblem(problem) .withMaxEvaluations(10000); Analyzer analyzer = new Analyzer() .withProblem(problem) .includeHypervolume() .showStatisticalSignificance(); //run each algorithm for 50 seeds for (String algorithm : algorithms) { analyzer.addAll(algorithm, executor.withAlgorithm(algorithm).runSeeds(50)); } //print the results analyzer.printAnalysis();
Running this script produces the output shown below. We can see that GDE3 and NSGA-II produce the best (largest) hypervolume values. Furthermore, we have determined statistically that there is no significant difference in performance between GDE3 and NSGA-II.
GDE3: Hypervolume: Min: 0.4389785065649592 Median: 0.4974186560778636 Max: 0.535166930530847 Count: 50 Indifferent: [NSGAII] eMOEA: Hypervolume: Min: 0.35003766343295073 Median: 0.47633216464734596 Max: 0.53311360537305 Count: 50 Indifferent: [] NSGAII: Hypervolume: Min: 0.38868701091987184 Median: 0.5040946740799506 Max: 0.5371138081508796 Count: 50 Indifferent: [GDE3]
Example 3: Collecting Runtime Dynamics
Runtime dynamics provide insight into the behavior of an optimization algorithm throughout a run. For instance, one can observe how solution quality changes with the number of function evaluations (NFE). The Instrumenter class records the runtime dynamics.
Instrumenter instrumenter = new Instrumenter() .withReferenceSet(new File("./pf/UF1.dat")) .withFrequency(100) .attachElapsedTimeCollector() .attachGenerationalDistanceCollector(); new Executor() .withProblem("UF1") .withAlgorithm("NSGAII") .withMaxEvaluations(10000) .withInstrumenter(instrumenter) .run(); Accumulator accumulator = instrumenter.getLastAccumulator(); for (int i=0; i<accumulator.size("NFE"); i++) { System.out.println(accumulator.get("NFE", i) + "\t" + accumulator.get("Elapsed Time", i) + "\t" + accumulator.get("GenerationalDistance", i)); }
The output from this script, shown below, shows how the generational distance metric changes over time. We see that NSGA-II is rapidly converging to the reference set (the optimal solutions) since its generational distance is converging to 0.
NFE Time Generational Distance 100 0.0256 0.7616 200 0.0421 0.6645 300 0.0543 0.4847 400 0.0636 0.4425 500 0.0713 0.4178 ...
Example 4: Defining New Problems
A number of methods are available to provide custom, user-defined problems that cleanly integrate with all other components of the MOEA Framework. The following demonstrates the two-objective DTLZ2 problem. Note that you only need to define two methods: newSolution and evaluate. The newSolution method defines the problem representation (the number and types of its decision variables). The evaluate method takes a solution and computes its objective function values.
public class DTLZ2 extends AbstractProblem { public DTLZ2() { super(11, 2); } public Solution newSolution() { Solution solution = new Solution(getNumberOfVariables(), getNumberOfObjectives()); for (int i = 0; i < getNumberOfVariables(); i++) { solution.setVariable(i, new RealVariable(0.0, 1.0)); } return solution; } public void evaluate(Solution solution) { double[] x = CoreUtils.castVariablesToDoubleArray(solution); double[] f = new double[numberOfObjectives]; int k = numberOfVariables - numberOfObjectives + 1; double g = 0.0; for (int i = numberOfVariables - k; i < numberOfVariables; i++) { g += Math.pow(x[i] - 0.5, 2.0); } for (int i = 0; i < numberOfObjectives; i++) { f[i] = 1.0 + g; for (int j = 0; j < numberOfObjectives - i - 1; j++) { f[i] *= Math.cos(0.5 * Math.PI * x[j]); } if (i != 0) { f[i] *= Math.sin(0.5 * Math.PI * x[numberOfObjectives - i - 1]); } } solution.setObjectives(f); } }
This can subsequently be used with the Executor.
NondominatedPopulation result = new Executor() .withProblemClass(DTLZ2.class) .withAlgorithm("GDE3") .withMaxEvaluations(10000) .distributeOnAllCores() .run();
Example 5: Defining Problems in C/C++
The MOEA Framework also supports defining new problems in other programming languages, such as C/C++. First, a Java stub for the problem must be created, as shown below. Note how the C/C++ executable is defined in the constructor.
public static class DTLZ2 extends ExternalProblem { public DTLZ2() throws IOException { super("./auxiliary/c/dtlz2_stdio.exe"); } @Override public String getName() { return "DTLZ2"; } @Override public int getNumberOfVariables() { return 11; } @Override public int getNumberOfObjectives() { return 2; } @Override public int getNumberOfConstraints() { return 0; } @Override public Solution newSolution() { Solution solution = new Solution(getNumberOfVariables(), getNumberOfObjectives()); for (int i = 0; i < getNumberOfVariables(); i++) { solution.setVariable(i, new RealVariable(0.0, 1.0)); } return solution; } }
Next, we create the C/C++ program that defines the problem. Note the use of
several methods with names beginning with the MOEA_ prefix. These methods
are provided by the moeaframework.h
library, which is
provided provided in the examples/ folder.
#include <stdio.h> #include <stdlib.h> #include <math.h> #include "moeaframework.h" #define PI 3.14159265358979323846 int nvars = 11; int nobjs = 2; void evaluate(double* vars, double* objs) { int i; int j; int k = nvars - nobjs + 1; double g = 0.0; for (i=nvars-k; i<nvars; i++) { g += pow(vars[i] - 0.5, 2.0); } for (i=0; i<nobjs; i++) { objs[i] = 1.0 + g; for (j=0; j<nobjs-i-1; j++) { objs[i] *= cos(0.5*PI*vars[j]); } if (i != 0) { objs[i] *= sin(0.5*PI*vars[nobjs-i-1]); } } } int main(int argc, char* argv[]) { double vars[nvars]; double objs[nobjs]; MOEA_Init(nobjs, 0); while (MOEA_Next_solution() == MOEA_SUCCESS) { MOEA_Read_doubles(nvars, vars); evaluate(vars, objs); MOEA_Write(objs, NULL); } MOEA_Terminate(); return EXIT_SUCCESS; }
The MOEA_Init method establishes a communication channel with the MOEA Framework. This communication channel can use standard I/O streams or sockets. The MOEA_Next_solution and MOEA_Read_doubles methods are used to read the next solution to be evaluated. Once the solution is evaluated, the computed objective function values are sent back to the MOEA Framework. Finally, once all solutions are evaluated, we shutdown the communication channel by calling MOEA_Terminate.
Concluding Remarks
We hope that you find the MOEA Framework useful. We strive to make this framework reliable and easy-to-use, and feedback from users like yourself help us meet these goals. If you encounter any issues using this software, please notify us.