1 package net.sf.openrocket.gui.dialogs.optimization;
3 import java.util.ArrayList;
5 import java.util.concurrent.LinkedBlockingQueue;
7 import javax.swing.SwingUtilities;
9 import net.sf.openrocket.document.Simulation;
10 import net.sf.openrocket.logging.LogHelper;
11 import net.sf.openrocket.optimization.general.FunctionOptimizer;
12 import net.sf.openrocket.optimization.general.OptimizationController;
13 import net.sf.openrocket.optimization.general.OptimizationException;
14 import net.sf.openrocket.optimization.general.ParallelExecutorCache;
15 import net.sf.openrocket.optimization.general.ParallelFunctionCache;
16 import net.sf.openrocket.optimization.general.Point;
17 import net.sf.openrocket.optimization.general.multidim.MultidirectionalSearchOptimizer;
18 import net.sf.openrocket.optimization.general.onedim.GoldenSectionSearchOptimizer;
19 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
20 import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
21 import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationFunction;
22 import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationListener;
23 import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
24 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
25 import net.sf.openrocket.startup.Application;
26 import net.sf.openrocket.unit.Value;
27 import net.sf.openrocket.util.BugException;
30 * A background worker that runs the optimization in the background. It supports providing
31 * evaluation and step counter information via listeners that are executed on the EDT.
33 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
35 public abstract class OptimizationWorker extends Thread implements OptimizationController, RocketOptimizationListener {
38 * Note: This is implemented as a separate Thread object instead of a SwingWorker because
39 * the SwingWorker cannot be interrupted in any way except by canceling the task, which
40 * makes it impossible to wait for its exiting (SwingWorker.get() throws a CancellationException
41 * if cancel() has been called).
43 * SwingWorker also seems to miss some chunks that have been provided to process() when the
46 * Nothing of this is documented, of course...
49 private static final LogHelper log = Application.getLogger();
51 /** Notify listeners every this many milliseconds */
52 private static final long PURGE_TIMEOUT = 500;
53 /** End optimization when step size is below this threshold */
54 private static final double STEP_SIZE_LIMIT = 0.005;
56 private final FunctionOptimizer optimizer;
57 private final RocketOptimizationFunction function;
59 private final Simulation simulation;
60 private final SimulationModifier[] modifiers;
62 private final ParallelFunctionCache cache;
65 private final LinkedBlockingQueue<FunctionEvaluationData> evaluationQueue =
66 new LinkedBlockingQueue<FunctionEvaluationData>();
67 private final LinkedBlockingQueue<OptimizationStepData> stepQueue =
68 new LinkedBlockingQueue<OptimizationStepData>();
69 private volatile long lastPurge = 0;
71 private OptimizationException optimizationException = null;
76 * @param simulation the simulation
77 * @param parameter the optimization parameter
78 * @param goal the optimization goal
79 * @param domain the optimization domain
80 * @param modifiers the simulation modifiers
82 public OptimizationWorker(Simulation simulation, OptimizableParameter parameter,
83 OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
85 this.simulation = simulation;
86 this.modifiers = modifiers.clone();
88 function = new RocketOptimizationFunction(simulation, parameter, goal, domain, modifiers);
89 function.addRocketOptimizationListener(this);
91 cache = new ParallelExecutorCache(1);
92 cache.setFunction(function);
94 if (modifiers.length == 1) {
95 optimizer = new GoldenSectionSearchOptimizer(cache);
97 optimizer = new MultidirectionalSearchOptimizer(cache);
106 double[] current = new double[modifiers.length];
107 for (int i = 0; i < modifiers.length; i++) {
108 current[i] = modifiers[i].getCurrentScaledValue(simulation);
110 Point initial = new Point(current);
112 optimizer.optimize(initial, this);
114 } catch (OptimizationException e) {
115 this.optimizationException = e;
117 SwingUtilities.invokeLater(new Runnable() {
120 lastPurge = System.currentTimeMillis() + 24L * 3600L * 1000L;
122 done(optimizationException);
129 * This method is called after the optimization has ended, either normally, when interrupted
130 * or by throwing an exception. This method is called on the EDT, like the done() method of SwingWorker.
132 * All data chunks to the listeners will be guaranteed to have been processed before calling done().
134 * @param exception a possible optimization exception that occurred, or <code>null</code> for normal exit.
136 protected abstract void done(OptimizationException exception);
140 * This method is called for each function evaluation that has taken place.
141 * This method is called on the EDT.
143 * @param data the data accumulated since the last call
145 protected abstract void functionEvaluated(List<FunctionEvaluationData> data);
148 * This method is called after each step taken by the optimization algorithm.
149 * This method is called on the EDT.
151 * @param data the data accumulated since the last call
153 protected abstract void optimizationStepTaken(List<OptimizationStepData> data);
157 * Publishes data to the listeners. The queue is purged every PURGE_TIMEOUT milliseconds.
159 * @param data the data to publish to the listeners
161 private synchronized void publish(FunctionEvaluationData evaluation, OptimizationStepData step) {
163 if (evaluation != null) {
164 evaluationQueue.add(evaluation);
170 // Add a method to the EDT to process the queue data
171 long now = System.currentTimeMillis();
172 if (lastPurge + PURGE_TIMEOUT <= now) {
174 SwingUtilities.invokeLater(new Runnable() {
186 * Process the queue and call the listeners. This method must always be called from the EDT.
188 private void processQueue() {
190 if (!SwingUtilities.isEventDispatchThread()) {
191 throw new BugException("processQueue called from non-EDT");
195 List<FunctionEvaluationData> evaluations = new ArrayList<FunctionEvaluationData>();
196 evaluationQueue.drainTo(evaluations);
197 if (!evaluations.isEmpty()) {
198 functionEvaluated(evaluations);
202 List<OptimizationStepData> steps = new ArrayList<OptimizationStepData>();
203 stepQueue.drainTo(steps);
204 if (!steps.isEmpty()) {
205 optimizationStepTaken(steps);
213 * NOTE: The stepTaken and evaluated methods may be called from other
214 * threads than the EDT or the SwingWorker thread!
218 public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
219 publish(null, new OptimizationStepData(oldPoint, oldValue, newPoint, newValue, stepSize));
221 if (stepSize < STEP_SIZE_LIMIT) {
222 log.info("stepSize=" + stepSize + " is below limit, ending optimization");
230 public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
231 publish(new FunctionEvaluationData(point, state, domainReference, parameterValue, goalValue), null);