b7d184457018a57e089b5af1cdb209cba8455151
[debian/openrocket] / 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.rocketoptimization.OptimizableParameter;
19 import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
20 import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationFunction;
21 import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationListener;
22 import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
23 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
24 import net.sf.openrocket.startup.Application;
25 import net.sf.openrocket.unit.Value;
26 import net.sf.openrocket.util.BugException;
27
28 /**
29  * A background worker that runs the optimization in the background.  It supports providing
30  * evaluation and step counter information via listeners that are executed on the EDT.
31  * 
32  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
33  */
34 public abstract class OptimizationWorker extends Thread implements OptimizationController, RocketOptimizationListener {
35         
36         /*
37          * Note:  This is implemented as a separate Thread object instead of a SwingWorker because
38          * the SwingWorker cannot be interrupted in any way except by canceling the task, which
39          * makes it impossible to wait for its exiting (SwingWorker.get() throws a CancellationException
40          * if cancel() has been called).
41          * 
42          * SwingWorker also seems to miss some chunks that have been provided to process() when the
43          * thread ends.
44          * 
45          * Nothing of this is documented, of course...
46          */
47
48         private static final LogHelper log = Application.getLogger();
49         
50         /** Notify listeners every this many milliseconds */
51         private static final long PURGE_TIMEOUT = 500;
52         /** End optimization when step size is below this threshold */
53         private static final double STEP_SIZE_LIMIT = 0.005;
54         
55         private final FunctionOptimizer optimizer;
56         private final RocketOptimizationFunction function;
57         
58         private final Simulation simulation;
59         private final SimulationModifier[] modifiers;
60         
61         private final ParallelFunctionCache cache;
62         
63
64         private final LinkedBlockingQueue<FunctionEvaluationData> evaluationQueue =
65                         new LinkedBlockingQueue<FunctionEvaluationData>();
66         private final LinkedBlockingQueue<OptimizationStepData> stepQueue =
67                         new LinkedBlockingQueue<OptimizationStepData>();
68         private volatile long lastPurge = 0;
69         
70         private OptimizationException optimizationException = null;
71         
72         
73         /**
74          * Sole constructor
75          * @param simulation    the simulation
76          * @param parameter                     the optimization parameter
77          * @param goal                          the optimization goal
78          * @param domain                        the optimization domain
79          * @param modifiers                     the simulation modifiers
80          */
81         public OptimizationWorker(Simulation simulation, OptimizableParameter parameter,
82                         OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
83                 
84                 this.simulation = simulation;
85                 this.modifiers = modifiers.clone();
86                 
87                 function = new RocketOptimizationFunction(simulation, parameter, goal, domain, modifiers);
88                 function.addRocketOptimizationListener(this);
89                 
90                 cache = new ParallelExecutorCache(1);
91                 cache.setFunction(function);
92                 
93                 optimizer = new MultidirectionalSearchOptimizer(cache);
94         }
95         
96         
97         @Override
98         public void run() {
99                 try {
100                         
101                         double[] current = new double[modifiers.length];
102                         for (int i = 0; i < modifiers.length; i++) {
103                                 current[i] = modifiers[i].getCurrentScaledValue(simulation);
104                         }
105                         Point initial = new Point(current);
106                         
107                         optimizer.optimize(initial, this);
108                         
109                 } catch (OptimizationException e) {
110                         this.optimizationException = e;
111                 } finally {
112                         SwingUtilities.invokeLater(new Runnable() {
113                                 @Override
114                                 public void run() {
115                                         lastPurge = System.currentTimeMillis() + 24L * 3600L * 1000L;
116                                         processQueue();
117                                         done(optimizationException);
118                                 }
119                         });
120                 }
121         }
122         
123         /**
124          * This method is called after the optimization has ended, either normally, when interrupted
125          * or by throwing an exception.  This method is called on the EDT, like the done() method of SwingWorker.
126          * <p>
127          * All data chunks to the listeners will be guaranteed to have been processed before calling done().
128          * 
129          * @param exception             a possible optimization exception that occurred, or <code>null</code> for normal exit.
130          */
131         protected abstract void done(OptimizationException exception);
132         
133         
134         /**
135          * This method is called for each function evaluation that has taken place.
136          * This method is called on the EDT.
137          * 
138          * @param data  the data accumulated since the last call
139          */
140         protected abstract void functionEvaluated(List<FunctionEvaluationData> data);
141         
142         /**
143          * This method is called after each step taken by the optimization algorithm.
144          * This method is called on the EDT.
145          * 
146          * @param data  the data accumulated since the last call
147          */
148         protected abstract void optimizationStepTaken(List<OptimizationStepData> data);
149         
150         
151         /**
152          * Publishes data to the listeners.  The queue is purged every PURGE_TIMEOUT milliseconds.
153          * 
154          * @param data  the data to publish to the listeners
155          */
156         private synchronized void publish(FunctionEvaluationData evaluation, OptimizationStepData step) {
157                 
158                 if (evaluation != null) {
159                         evaluationQueue.add(evaluation);
160                 }
161                 if (step != null) {
162                         stepQueue.add(step);
163                 }
164                 
165                 // Add a method to the EDT to process the queue data
166                 long now = System.currentTimeMillis();
167                 if (lastPurge + PURGE_TIMEOUT <= now) {
168                         lastPurge = now;
169                         SwingUtilities.invokeLater(new Runnable() {
170                                 @Override
171                                 public void run() {
172                                         processQueue();
173                                 }
174                         });
175                 }
176                 
177         }
178         
179         
180         /**
181          * Process the queue and call the listeners.  This method must always be called from the EDT.
182          */
183         private void processQueue() {
184                 
185                 if (!SwingUtilities.isEventDispatchThread()) {
186                         throw new BugException("processQueue called from non-EDT");
187                 }
188                 
189
190                 List<FunctionEvaluationData> evaluations = new ArrayList<FunctionEvaluationData>();
191                 evaluationQueue.drainTo(evaluations);
192                 if (!evaluations.isEmpty()) {
193                         functionEvaluated(evaluations);
194                 }
195                 
196
197                 List<OptimizationStepData> steps = new ArrayList<OptimizationStepData>();
198                 stepQueue.drainTo(steps);
199                 if (!steps.isEmpty()) {
200                         optimizationStepTaken(steps);
201                 }
202         }
203         
204         
205
206
207         /*
208          * NOTE:  The stepTaken and evaluated methods may be called from other
209          * threads than the EDT or the SwingWorker thread!
210          */
211
212         @Override
213         public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
214                 publish(null, new OptimizationStepData(oldPoint, oldValue, newPoint, newValue, stepSize));
215                 
216                 if (stepSize < STEP_SIZE_LIMIT) {
217                         log.info("stepSize=" + stepSize + " is below limit, ending optimization");
218                         return false;
219                 } else {
220                         return true;
221                 }
222         }
223         
224         @Override
225         public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
226                 publish(new FunctionEvaluationData(point, state, domainReference, parameterValue, goalValue), null);
227         }
228         
229 }