1 package net.sf.openrocket.optimization.general.onedim;
3 import net.sf.openrocket.logging.LogHelper;
4 import net.sf.openrocket.optimization.general.FunctionCache;
5 import net.sf.openrocket.optimization.general.FunctionOptimizer;
6 import net.sf.openrocket.optimization.general.OptimizationController;
7 import net.sf.openrocket.optimization.general.OptimizationException;
8 import net.sf.openrocket.optimization.general.ParallelFunctionCache;
9 import net.sf.openrocket.optimization.general.Point;
10 import net.sf.openrocket.startup.Application;
11 import net.sf.openrocket.util.MathUtil;
12 import net.sf.openrocket.util.Statistics;
15 * An implementation of a one-dimensional golden section search method
16 * (see e.g. Nonlinear programming, Bazaraa, Sherall, Shetty, 2nd edition, p. 270)
18 * This implementation attempts to guess future evaluations and computes them in parallel
19 * with the next point.
21 * The optimization can be aborted by interrupting the current thread.
23 public class GoldenSectionSearchOptimizer implements FunctionOptimizer, Statistics {
24 private static final LogHelper log = Application.getLogger();
26 private static final double ALPHA = (Math.sqrt(5) - 1) / 2.0;
29 private ParallelFunctionCache functionExecutor;
31 private Point current = null;
33 private int guessSuccess = 0;
34 private int guessFailure = 0;
38 * Construct an optimizer with no function executor.
40 public GoldenSectionSearchOptimizer() {
45 * Construct an optimizer.
47 * @param functionExecutor the function executor.
49 public GoldenSectionSearchOptimizer(ParallelFunctionCache functionExecutor) {
51 this.functionExecutor = functionExecutor;
55 public void optimize(Point initial, OptimizationController control) throws OptimizationException {
57 if (initial.dim() != 1) {
58 throw new IllegalArgumentException("Only single-dimensional optimization supported, dim=" +
62 log.info("Starting golden section search for optimization");
70 Point previous = p(0);
71 double previousValue = Double.NaN;
73 double currentValue = Double.NaN;
76 * Initialize the points + computation.
80 Point b = section1(a, d);
81 Point c = section2(a, d);
83 functionExecutor.compute(a);
84 functionExecutor.compute(d);
85 functionExecutor.compute(b);
86 functionExecutor.compute(c);
88 // Wait for points a and d, which normally are already precomputed
89 functionExecutor.waitFor(a);
90 functionExecutor.waitFor(d);
92 boolean continueOptimization = true;
93 while (continueOptimization) {
96 * Get values at A & D for guessing.
97 * These are pre-calculated except during the first step.
100 fa = functionExecutor.getValue(a);
101 fd = functionExecutor.getValue(d);
105 * Start calculating possible two next points. The order of evaluation
106 * is selected based on the function values at A and D.
108 guessAC = section1(a, c);
109 guessBD = section2(b, d);
110 System.err.println("Queueing " + guessAC + " and " + guessBD);
111 if (Double.isNaN(fd) || fa < fd) {
113 functionExecutor.compute(guessAC);
114 functionExecutor.compute(guessBD);
117 functionExecutor.compute(guessBD);
118 functionExecutor.compute(guessAC);
123 * Get values at B and C.
126 functionExecutor.waitFor(b);
127 functionExecutor.waitFor(c);
128 fb = functionExecutor.getValue(b);
129 fc = functionExecutor.getValue(c);
131 double min = MathUtil.min(fa, fb, fc, fd);
132 if (Double.isNaN(min)) {
133 throw new OptimizationException("Unable to compute initial function values");
138 * Update previous and current values for step control.
140 previousValue = currentValue;
145 } else if (min == fb) {
147 } else if (min == fc) {
155 * Select next positions. These are already being calculated in the background
156 * as guessAC and guessBD.
158 if (min == fa || min == fb) {
162 functionExecutor.abort(guessBD);
164 log.debug("Selecting A-C region, a=" + a.get(0) + " c=" + c.get(0));
174 functionExecutor.abort(guessAC);
176 log.debug("Selecting B-D region, b=" + b.get(0) + " d=" + d.get(0));
186 * Check optimization control.
188 continueOptimization = control.stepTaken(previous, previousValue,
189 current, currentValue, c.get(0) - a.get(0));
191 if (Thread.interrupted()) {
192 throw new InterruptedException();
198 } catch (InterruptedException e) {
199 log.info("Optimization was interrupted with InterruptedException");
202 if (guessAC != null) {
203 System.err.println("Aborting " + guessAC);
204 functionExecutor.abort(guessAC);
206 if (guessBD != null) {
207 System.err.println("Aborting " + guessBD);
208 functionExecutor.abort(guessBD);
212 log.info("Finishing optimization at point " + getOptimumPoint() + " value " + getOptimumValue());
213 log.info("Optimization statistics: " + getStatistics());
217 private Point p(double v) {
222 private Point section1(Point a, Point b) {
223 double va = a.get(0);
224 double vb = b.get(0);
225 return p(va + (1 - ALPHA) * (vb - va));
228 private Point section2(Point a, Point b) {
229 double va = a.get(0);
230 double vb = b.get(0);
231 return p(va + ALPHA * (vb - va));
237 public Point getOptimumPoint() {
243 public double getOptimumValue() {
244 if (getOptimumPoint() == null) {
247 return functionExecutor.getValue(getOptimumPoint());
251 public FunctionCache getFunctionCache() {
252 return functionExecutor;
256 public void setFunctionCache(FunctionCache functionCache) {
257 if (!(functionCache instanceof ParallelFunctionCache)) {
258 throw new IllegalArgumentException("Function cache needs to be a ParallelFunctionCache: " + functionCache);
260 this.functionExecutor = (ParallelFunctionCache) functionCache;
264 public String getStatistics() {
265 return String.format("Guess hit rate %d/%d = %.3f", guessSuccess, guessSuccess + guessFailure,
266 ((double) guessSuccess) / (guessSuccess + guessFailure));
270 public void resetStatistics() {