create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / dialogs / optimization / OptimizationWorker.java
1 package net.sf.openrocket.gui.dialogs.optimization;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.concurrent.LinkedBlockingQueue;
6
7 import javax.swing.SwingUtilities;
8
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;
28
29 /**
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.
32  * 
33  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
34  */
35 public abstract class OptimizationWorker extends Thread implements OptimizationController, RocketOptimizationListener {
36         
37         /*
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).
42          * 
43          * SwingWorker also seems to miss some chunks that have been provided to process() when the
44          * thread ends.
45          * 
46          * Nothing of this is documented, of course...
47          */
48
49         private static final LogHelper log = Application.getLogger();
50         
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;
55         
56         private final FunctionOptimizer optimizer;
57         private final RocketOptimizationFunction function;
58         
59         private final Simulation simulation;
60         private final SimulationModifier[] modifiers;
61         
62         private final ParallelFunctionCache cache;
63         
64
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;
70         
71         private OptimizationException optimizationException = null;
72         
73         
74         /**
75          * Sole constructor
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
81          */
82         public OptimizationWorker(Simulation simulation, OptimizableParameter parameter,
83                         OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
84                 
85                 this.simulation = simulation;
86                 this.modifiers = modifiers.clone();
87                 
88                 function = new RocketOptimizationFunction(simulation, parameter, goal, domain, modifiers);
89                 function.addRocketOptimizationListener(this);
90                 
91                 cache = new ParallelExecutorCache(1);
92                 cache.setFunction(function);
93                 
94                 if (modifiers.length == 1) {
95                         optimizer = new GoldenSectionSearchOptimizer(cache);
96                 } else {
97                         optimizer = new MultidirectionalSearchOptimizer(cache);
98                 }
99         }
100         
101         
102         @Override
103         public void run() {
104                 try {
105                         
106                         double[] current = new double[modifiers.length];
107                         for (int i = 0; i < modifiers.length; i++) {
108                                 current[i] = modifiers[i].getCurrentScaledValue(simulation);
109                         }
110                         Point initial = new Point(current);
111                         
112                         optimizer.optimize(initial, this);
113                         
114                 } catch (OptimizationException e) {
115                         this.optimizationException = e;
116                 } finally {
117                         SwingUtilities.invokeLater(new Runnable() {
118                                 @Override
119                                 public void run() {
120                                         lastPurge = System.currentTimeMillis() + 24L * 3600L * 1000L;
121                                         processQueue();
122                                         done(optimizationException);
123                                 }
124                         });
125                 }
126         }
127         
128         /**
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.
131          * <p>
132          * All data chunks to the listeners will be guaranteed to have been processed before calling done().
133          * 
134          * @param exception             a possible optimization exception that occurred, or <code>null</code> for normal exit.
135          */
136         protected abstract void done(OptimizationException exception);
137         
138         
139         /**
140          * This method is called for each function evaluation that has taken place.
141          * This method is called on the EDT.
142          * 
143          * @param data  the data accumulated since the last call
144          */
145         protected abstract void functionEvaluated(List<FunctionEvaluationData> data);
146         
147         /**
148          * This method is called after each step taken by the optimization algorithm.
149          * This method is called on the EDT.
150          * 
151          * @param data  the data accumulated since the last call
152          */
153         protected abstract void optimizationStepTaken(List<OptimizationStepData> data);
154         
155         
156         /**
157          * Publishes data to the listeners.  The queue is purged every PURGE_TIMEOUT milliseconds.
158          * 
159          * @param data  the data to publish to the listeners
160          */
161         private synchronized void publish(FunctionEvaluationData evaluation, OptimizationStepData step) {
162                 
163                 if (evaluation != null) {
164                         evaluationQueue.add(evaluation);
165                 }
166                 if (step != null) {
167                         stepQueue.add(step);
168                 }
169                 
170                 // Add a method to the EDT to process the queue data
171                 long now = System.currentTimeMillis();
172                 if (lastPurge + PURGE_TIMEOUT <= now) {
173                         lastPurge = now;
174                         SwingUtilities.invokeLater(new Runnable() {
175                                 @Override
176                                 public void run() {
177                                         processQueue();
178                                 }
179                         });
180                 }
181                 
182         }
183         
184         
185         /**
186          * Process the queue and call the listeners.  This method must always be called from the EDT.
187          */
188         private void processQueue() {
189                 
190                 if (!SwingUtilities.isEventDispatchThread()) {
191                         throw new BugException("processQueue called from non-EDT");
192                 }
193                 
194
195                 List<FunctionEvaluationData> evaluations = new ArrayList<FunctionEvaluationData>();
196                 evaluationQueue.drainTo(evaluations);
197                 if (!evaluations.isEmpty()) {
198                         functionEvaluated(evaluations);
199                 }
200                 
201
202                 List<OptimizationStepData> steps = new ArrayList<OptimizationStepData>();
203                 stepQueue.drainTo(steps);
204                 if (!steps.isEmpty()) {
205                         optimizationStepTaken(steps);
206                 }
207         }
208         
209         
210
211
212         /*
213          * NOTE:  The stepTaken and evaluated methods may be called from other
214          * threads than the EDT or the SwingWorker thread!
215          */
216
217         @Override
218         public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
219                 publish(null, new OptimizationStepData(oldPoint, oldValue, newPoint, newValue, stepSize));
220                 
221                 if (stepSize < STEP_SIZE_LIMIT) {
222                         log.info("stepSize=" + stepSize + " is below limit, ending optimization");
223                         return false;
224                 } else {
225                         return true;
226                 }
227         }
228         
229         @Override
230         public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
231                 publish(new FunctionEvaluationData(point, state, domainReference, parameterValue, goalValue), null);
232         }
233         
234 }