1 package net.sf.openrocket.optimization.general.multidim;
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.LinkedList;
8 import net.sf.openrocket.logging.LogHelper;
9 import net.sf.openrocket.optimization.general.FunctionCache;
10 import net.sf.openrocket.optimization.general.FunctionOptimizer;
11 import net.sf.openrocket.optimization.general.OptimizationController;
12 import net.sf.openrocket.optimization.general.OptimizationException;
13 import net.sf.openrocket.optimization.general.ParallelFunctionCache;
14 import net.sf.openrocket.optimization.general.Point;
15 import net.sf.openrocket.startup.Application;
16 import net.sf.openrocket.util.Statistics;
19 * A customized implementation of the parallel multidirectional search algorithm by Dennis and Torczon.
21 * This is a parallel pattern search optimization algorithm. The function evaluations are performed
22 * using an ExecutorService. By default a ThreadPoolExecutor is used that has as many thread defined
23 * as the system has processors.
25 * The optimization can be aborted by interrupting the current thread.
27 public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Statistics {
28 private static final LogHelper log = Application.getLogger();
30 private List<Point> simplex = new ArrayList<Point>();
32 private ParallelFunctionCache functionExecutor;
34 private boolean useExpansion = false;
35 private boolean useCoordinateSearch = false;
37 private int stepCount = 0;
38 private int reflectionAcceptance = 0;
39 private int expansionAcceptance = 0;
40 private int coordinateAcceptance = 0;
41 private int reductionFallback = 0;
44 public MultidirectionalSearchOptimizer() {
48 public MultidirectionalSearchOptimizer(ParallelFunctionCache functionCache) {
49 this.functionExecutor = functionCache;
55 public void optimize(Point initial, OptimizationController control) throws OptimizationException {
56 FunctionCacheComparator comparator = new FunctionCacheComparator(functionExecutor);
58 final List<Point> pattern = SearchPattern.square(initial.dim());
59 log.info("Starting optimization at " + initial + " with pattern " + pattern);
63 boolean simplexComputed = false;
66 // Set up the current simplex
69 for (Point p : pattern) {
70 simplex.add(initial.add(p.mul(step)));
74 List<Point> reflection = new ArrayList<Point>(simplex.size());
75 List<Point> expansion = new ArrayList<Point>(simplex.size());
76 List<Point> coordinateSearch = new ArrayList<Point>(simplex.size());
79 boolean continueOptimization = true;
80 while (continueOptimization) {
82 log.debug("Starting optimization step with simplex " + simplex +
83 (simplexComputed ? "" : " (not computed)"));
86 if (!simplexComputed) {
87 // TODO: Could something be computed in parallel?
88 functionExecutor.compute(simplex);
89 functionExecutor.waitFor(simplex);
90 Collections.sort(simplex, comparator);
91 simplexComputed = true;
94 current = simplex.get(0);
95 currentValue = functionExecutor.getValue(current);
98 * Compute and queue the next points in likely order of usefulness.
99 * Expansion is unlikely as we're mainly dealing with bounded optimization.
101 createReflection(simplex, reflection);
102 if (useCoordinateSearch)
103 createCoordinateSearch(current, step, coordinateSearch);
105 createExpansion(simplex, expansion);
107 functionExecutor.compute(reflection);
108 if (useCoordinateSearch)
109 functionExecutor.compute(coordinateSearch);
111 functionExecutor.compute(expansion);
113 // Check reflection acceptance
114 System.err.println("stepsize = " + step);
115 System.err.println("Simplex = " + simplex);
116 System.err.println("Reflection = " + reflection);
117 log.debug("Computing reflection");
118 functionExecutor.waitFor(reflection);
120 if (accept(reflection, currentValue)) {
122 log.debug("Reflection was successful, aborting coordinate search, " +
123 (useExpansion ? "computing" : "skipping") + " expansion");
125 if (useCoordinateSearch)
126 functionExecutor.abort(coordinateSearch);
129 simplex.add(current);
130 simplex.addAll(reflection);
131 Collections.sort(simplex, comparator);
136 * Assume expansion to be unsuccessful, queue next reflection while computing expansion.
138 createReflection(simplex, reflection);
140 functionExecutor.compute(reflection);
141 functionExecutor.waitFor(expansion);
143 if (accept(expansion, currentValue)) {
144 log.debug("Expansion was successful, aborting reflection");
145 functionExecutor.abort(reflection);
148 simplex.add(current);
149 simplex.addAll(expansion);
151 Collections.sort(simplex, comparator);
152 expansionAcceptance++;
154 log.debug("Expansion failed");
155 reflectionAcceptance++;
159 reflectionAcceptance++;
164 log.debug("Reflection was unsuccessful, aborting expansion, computing coordinate search");
166 functionExecutor.abort(expansion);
169 * Assume coordinate search to be unsuccessful, queue contraction step while computing.
172 functionExecutor.compute(simplex);
174 if (useCoordinateSearch) {
175 functionExecutor.waitFor(coordinateSearch);
177 if (accept(coordinateSearch, currentValue)) {
179 log.debug("Coordinate search successful, reseting simplex");
180 List<Point> toAbort = new LinkedList<Point>(simplex);
182 simplex.add(current);
183 for (Point p : pattern) {
184 simplex.add(current.add(p.mul(step)));
186 toAbort.removeAll(simplex);
187 functionExecutor.abort(toAbort);
188 simplexComputed = false;
189 coordinateAcceptance++;
192 log.debug("Coordinate search unsuccessful, halving step.");
194 simplexComputed = false;
198 log.debug("Coordinate search not used, halving step.");
200 simplexComputed = false;
206 log.debug("Ending optimization step with simplex " + simplex);
208 continueOptimization = control.stepTaken(current, currentValue, simplex.get(0),
209 functionExecutor.getValue(simplex.get(0)), step);
211 if (Thread.interrupted()) {
212 throw new InterruptedException();
217 } catch (InterruptedException e) {
218 log.info("Optimization was interrupted with InterruptedException");
221 log.info("Finishing optimization at point " + simplex.get(0) + " value = " +
222 functionExecutor.getValue(simplex.get(0)));
223 log.info("Optimization statistics: " + getStatistics());
228 private void createReflection(List<Point> base, List<Point> reflection) {
229 Point current = base.get(0);
232 /* new = - (old - current) + current = 2*current - old */
233 for (int i = 1; i < base.size(); i++) {
234 Point p = base.get(i);
235 p = current.mul(2).sub(p);
240 private void createExpansion(List<Point> base, List<Point> expansion) {
241 Point current = base.get(0);
243 for (int i = 1; i < base.size(); i++) {
244 Point p = current.mul(3).sub(base.get(i).mul(2));
249 private void halveStep(List<Point> base) {
250 Point current = base.get(0);
251 for (int i = 1; i < base.size(); i++) {
252 Point p = base.get(i);
254 /* new = (old - current)*0.5 + current = old*0.5 + current*0.5 = (old + current)*0.5 */
256 p = p.add(current).mul(0.5);
261 private void createCoordinateSearch(Point current, double step, List<Point> coordinateDirections) {
262 coordinateDirections.clear();
263 for (int i = 0; i < current.dim(); i++) {
264 Point p = new Point(current.dim());
266 coordinateDirections.add(current.add(p));
267 coordinateDirections.add(current.sub(p));
272 private boolean accept(List<Point> points, double currentValue) {
273 for (Point p : points) {
274 if (functionExecutor.getValue(p) < currentValue) {
284 public Point getOptimumPoint() {
285 if (simplex.size() == 0) {
286 throw new IllegalStateException("Optimization has not been called, simplex is empty");
288 return simplex.get(0);
292 public double getOptimumValue() {
293 return functionExecutor.getValue(getOptimumPoint());
297 public FunctionCache getFunctionCache() {
298 return functionExecutor;
302 public void setFunctionCache(FunctionCache functionCache) {
303 if (!(functionCache instanceof ParallelFunctionCache)) {
304 throw new IllegalArgumentException("Function cache needs to be a ParallelFunctionCache: " + functionCache);
306 this.functionExecutor = (ParallelFunctionCache) functionCache;
310 public String getStatistics() {
311 return "MultidirectionalSearchOptimizer[stepCount=" + stepCount +
312 ", reflectionAcceptance=" + reflectionAcceptance +
313 ", expansionAcceptance=" + expansionAcceptance +
314 ", coordinateAcceptance=" + coordinateAcceptance +
315 ", reductionFallback=" + reductionFallback;
319 public void resetStatistics() {
321 reflectionAcceptance = 0;
322 expansionAcceptance = 0;
323 coordinateAcceptance = 0;
324 reductionFallback = 0;