optimization updates
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Tue, 9 Aug 2011 19:57:15 +0000 (19:57 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Tue, 9 Aug 2011 19:57:15 +0000 (19:57 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@148 180e2498-e6e9-4542-8430-84ac67f01cd8

18 files changed:
ChangeLog
build.xml
src/net/sf/openrocket/gui/components/DoubleCellEditor.java
src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java
src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java
src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java
src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java
src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java
src/net/sf/openrocket/optimization/general/onedim/GoldenSectionSearchOptimizer.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java
src/net/sf/openrocket/util/MathUtil.java
test/net/sf/openrocket/l10n/TestResourceBundleTranslator.java [new file with mode: 0644]
test/net/sf/openrocket/util/MathUtilTest.java

index 3b3ed8f1a4706d0b7f6299b280f82828d46a0811..71e4ddcfc7dbe8352ac19c496f1f5da25b5c2680 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2011-08-08  Sampo Niskanen
+
+       * Enhanced one-dimensional optimization algorithm
+       * [BUG] l10n/ directory not included in source distribution
+
 2011-08-07  Sampo Niskanen
 
        * Optimization implementation
index 84462ba713150f6ef3819f6baaa66c5e1abb7842..c716a528daccb06f073d3aa57c7853700e9b6590 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -89,7 +89,7 @@
                        <fileset dir="." includes="*">
                                <type type="file"/>
                        </fileset>
-                       <fileset dir="." includes="datafiles/ lib/ lib-test/ pix/ src/ test/"/>
+                       <fileset dir="." includes="datafiles/ lib/ lib-test/ pix/ src/ test/ l10n/"/>
                </copy>
                <zip destfile="${dist.src}" basedir="${build.dir}" includes="${pkgname}/"/>
                <delete dir="${build.dir}/${pkgname}"/>
index 6ff86822e694454cdcf75a9fc787ecfb1c1cff60..1602350acdf084a1eca117e68a169936d3143725 100644 (file)
@@ -1,6 +1,7 @@
 package net.sf.openrocket.gui.components;
 
 import java.awt.Component;
+import java.text.ParseException;
 
 import javax.swing.AbstractCellEditor;
 import javax.swing.JSpinner;
@@ -34,6 +35,17 @@ public class DoubleCellEditor extends AbstractCellEditor implements TableCellEdi
        }
        
        
+       @Override
+       public boolean stopCellEditing() {
+               try {
+                       editor.commitEdit();
+               } catch (ParseException e) {
+                       // Ignore
+               }
+               return super.stopCellEditing();
+       }
+       
+       
        @Override
        public Object getCellEditorValue() {
                return model.getValue();
index fe0e37744ad17b37974d708efb2035739fe8f49e..f60c720d80e17bce2594e194120cd533fae66b0f 100644 (file)
@@ -95,6 +95,8 @@ import net.sf.openrocket.util.TextUtil;
 
 import com.itextpdf.text.Font;
 
+// FIXME: Override to zero mass produces NaN in simulation
+
 /**
  * General rocket optimization dialog.
  * 
@@ -937,7 +939,10 @@ public class GeneralOptimizationDialog extends JDialog {
                        if (newModifiers != null) {
                                int index = newModifiers.indexOf(original);
                                if (index >= 0) {
-                                       newSelected.add(newModifiers.get(index));
+                                       SimulationModifier updated = newModifiers.get(index);
+                                       updated.setMinValue(original.getMinValue());
+                                       updated.setMaxValue(original.getMaxValue());
+                                       newSelected.add(updated);
                                }
                        }
                }
index b7d184457018a57e089b5af1cdb209cba8455151..eda1f9ae9637cae8369e7ab6f7429a5e6c1c3c20 100644 (file)
@@ -15,6 +15,7 @@ import net.sf.openrocket.optimization.general.ParallelExecutorCache;
 import net.sf.openrocket.optimization.general.ParallelFunctionCache;
 import net.sf.openrocket.optimization.general.Point;
 import net.sf.openrocket.optimization.general.multidim.MultidirectionalSearchOptimizer;
+import net.sf.openrocket.optimization.general.onedim.GoldenSectionSearchOptimizer;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
 import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationFunction;
@@ -90,7 +91,11 @@ public abstract class OptimizationWorker extends Thread implements OptimizationC
                cache = new ParallelExecutorCache(1);
                cache.setFunction(function);
                
-               optimizer = new MultidirectionalSearchOptimizer(cache);
+               if (modifiers.length == 1) {
+                       optimizer = new GoldenSectionSearchOptimizer(cache);
+               } else {
+                       optimizer = new MultidirectionalSearchOptimizer(cache);
+               }
        }
        
        
index 64b88ff167302d9c3e12ac3daa2a49930a2a3c99..67154da545947aabebf2c29a139bb2e3e1c4d2e9 100644 (file)
@@ -3,6 +3,7 @@ package net.sf.openrocket.optimization.general;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -201,6 +202,30 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
        }
        
        
+       @Override
+       public void abortAll() {
+               Iterator<Point> iterator = futureMap.keySet().iterator();
+               while (iterator.hasNext()) {
+                       Point point = iterator.next();
+                       Future<Double> future = futureMap.get(point);
+                       iterator.remove();
+                       
+                       if (future.isDone()) {
+                               // Evaluation has been completed, store value in cache
+                               try {
+                                       double value = future.get();
+                                       functionCache.put(point, value);
+                               } catch (Exception e) {
+                                       // Ignore
+                               }
+                       } else {
+                               // Cancel the evaluation
+                               future.cancel(true);
+                       }
+               }
+       }
+       
+       
        @Override
        public double getValue(Point point) {
                if (isOutsideRange(point)) {
@@ -274,4 +299,5 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
                }
        }
        
+
 }
index 05dcba23e2f104b5a904aff45f1bc2f70153b102..1c8a35f64fe81bbae6d9204653e6e559c52dde4d 100644 (file)
@@ -68,4 +68,10 @@ public interface ParallelFunctionCache extends FunctionCache {
         * @return                      <code>true</code> if the point has been computed anyway, <code>false</code> if not.
         */
        public boolean abort(Point point);
+       
+       
+       /**
+        * Abort the computation of all still unexecuted points.
+        */
+       public void abortAll();
 }
index 1f15800f823757c17843f849ff1debe5932e3dd1..76d2f301ac8aabbabb69f31b0d2d63290f842dcf 100644 (file)
@@ -215,6 +215,7 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
                
                log.info("Finishing optimization at point " + simplex.get(0) + " value = " +
                                functionExecutor.getValue(simplex.get(0)));
+               log.info("Optimization statistics: " + getStatistics());
        }
        
        
diff --git a/src/net/sf/openrocket/optimization/general/onedim/GoldenSectionSearchOptimizer.java b/src/net/sf/openrocket/optimization/general/onedim/GoldenSectionSearchOptimizer.java
new file mode 100644 (file)
index 0000000..853ffbf
--- /dev/null
@@ -0,0 +1,275 @@
+package net.sf.openrocket.optimization.general.onedim;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.FunctionCache;
+import net.sf.openrocket.optimization.general.FunctionOptimizer;
+import net.sf.openrocket.optimization.general.OptimizationController;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.general.ParallelFunctionCache;
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Statistics;
+
+/**
+ * An implementation of a one-dimensional golden section search method
+ * (see e.g. Nonlinear programming, Bazaraa, Sherall, Shetty, 2nd edition, p. 270)
+ * <p>
+ * This implementation attempts to guess future evaluations and computes them in parallel
+ * with the next point.
+ * <p>
+ * The optimization can be aborted by interrupting the current thread.
+ */
+public class GoldenSectionSearchOptimizer implements FunctionOptimizer, Statistics {
+       private static final LogHelper log = Application.getLogger();
+       
+       private static final double ALPHA = (Math.sqrt(5) - 1) / 2.0;
+       
+
+       private ParallelFunctionCache functionExecutor;
+       
+       private Point current = null;
+       
+       private int guessSuccess = 0;
+       private int guessFailure = 0;
+       
+       
+       /**
+        * Construct an optimizer with no function executor.
+        */
+       public GoldenSectionSearchOptimizer() {
+               // No-op
+       }
+       
+       /**
+        * Construct an optimizer.
+        * 
+        * @param functionExecutor      the function executor.
+        */
+       public GoldenSectionSearchOptimizer(ParallelFunctionCache functionExecutor) {
+               super();
+               this.functionExecutor = functionExecutor;
+       }
+       
+       @Override
+       public void optimize(Point initial, OptimizationController control) throws OptimizationException {
+               
+               if (initial.dim() != 1) {
+                       throw new IllegalArgumentException("Only single-dimensional optimization supported, dim=" +
+                                       initial.dim());
+               }
+               
+               log.info("Starting golden section search for optimization");
+               
+               Point guessAC = null;
+               Point guessBD = null;
+               
+               try {
+                       boolean guessedAC;
+                       
+                       Point previous = p(0);
+                       double previousValue = Double.NaN;
+                       current = previous;
+                       double currentValue = Double.NaN;
+                       
+                       /*
+                        * Initialize the points + computation.
+                        */
+                       Point a = p(0);
+                       Point d = p(1.0);
+                       Point b = section1(a, d);
+                       Point c = section2(a, d);
+                       
+                       functionExecutor.compute(a);
+                       functionExecutor.compute(d);
+                       functionExecutor.compute(b);
+                       functionExecutor.compute(c);
+                       
+                       // Wait for points a and d, which normally are already precomputed
+                       functionExecutor.waitFor(a);
+                       functionExecutor.waitFor(d);
+                       
+                       boolean continueOptimization = true;
+                       while (continueOptimization) {
+                               
+                               /*
+                                * Get values at A & D for guessing.
+                                * These are pre-calculated except during the first step.
+                                */
+                               double fa, fd;
+                               fa = functionExecutor.getValue(a);
+                               fd = functionExecutor.getValue(d);
+                               
+
+                               /*
+                                * Start calculating possible two next points.  The order of evaluation
+                                * is selected based on the function values at A and D.
+                                */
+                               guessAC = section1(a, c);
+                               guessBD = section2(b, d);
+                               System.err.println("Queueing " + guessAC + " and " + guessBD);
+                               if (Double.isNaN(fd) || fa < fd) {
+                                       guessedAC = true;
+                                       functionExecutor.compute(guessAC);
+                                       functionExecutor.compute(guessBD);
+                               } else {
+                                       guessedAC = false;
+                                       functionExecutor.compute(guessBD);
+                                       functionExecutor.compute(guessAC);
+                               }
+                               
+
+                               /*
+                                * Get values at B and C.
+                                */
+                               double fb, fc;
+                               functionExecutor.waitFor(b);
+                               functionExecutor.waitFor(c);
+                               fb = functionExecutor.getValue(b);
+                               fc = functionExecutor.getValue(c);
+                               
+                               double min = MathUtil.min(fa, fb, fc, fd);
+                               if (Double.isNaN(min)) {
+                                       throw new OptimizationException("Unable to compute initial function values");
+                               }
+                               
+
+                               /*
+                                * Update previous and current values for step control.
+                                */
+                               previousValue = currentValue;
+                               currentValue = min;
+                               previous = current;
+                               if (min == fa) {
+                                       current = a;
+                               } else if (min == fb) {
+                                       current = b;
+                               } else if (min == fc) {
+                                       current = c;
+                               } else {
+                                       current = d;
+                               }
+                               
+
+                               /*
+                                * Select next positions.  These are already being calculated in the background
+                                * as guessAC and guessBD.
+                                */
+                               if (min == fa || min == fb) {
+                                       d = c;
+                                       c = b;
+                                       b = guessAC;
+                                       functionExecutor.abort(guessBD);
+                                       guessBD = null;
+                                       log.debug("Selecting A-C region, a=" + a.get(0) + " c=" + c.get(0));
+                                       if (guessedAC) {
+                                               guessSuccess++;
+                                       } else {
+                                               guessFailure++;
+                                       }
+                               } else {
+                                       a = b;
+                                       b = c;
+                                       c = guessBD;
+                                       functionExecutor.abort(guessAC);
+                                       guessAC = null;
+                                       log.debug("Selecting B-D region, b=" + b.get(0) + " d=" + d.get(0));
+                                       if (!guessedAC) {
+                                               guessSuccess++;
+                                       } else {
+                                               guessFailure++;
+                                       }
+                               }
+                               
+
+                               /*
+                                * Check optimization control.
+                                */
+                               continueOptimization = control.stepTaken(previous, previousValue,
+                                               current, currentValue, c.get(0) - a.get(0));
+                               
+                               if (Thread.interrupted()) {
+                                       throw new InterruptedException();
+                               }
+                               
+                       }
+                       
+
+               } catch (InterruptedException e) {
+                       log.info("Optimization was interrupted with InterruptedException");
+               }
+               
+               if (guessAC != null) {
+                       System.err.println("Aborting " + guessAC);
+                       functionExecutor.abort(guessAC);
+               }
+               if (guessBD != null) {
+                       System.err.println("Aborting " + guessBD);
+                       functionExecutor.abort(guessBD);
+               }
+               
+
+               log.info("Finishing optimization at point " + getOptimumPoint() + " value " + getOptimumValue());
+               log.info("Optimization statistics: " + getStatistics());
+       }
+       
+       
+       private Point p(double v) {
+               return new Point(v);
+       }
+       
+       
+       private Point section1(Point a, Point b) {
+               double va = a.get(0);
+               double vb = b.get(0);
+               return p(va + (1 - ALPHA) * (vb - va));
+       }
+       
+       private Point section2(Point a, Point b) {
+               double va = a.get(0);
+               double vb = b.get(0);
+               return p(va + ALPHA * (vb - va));
+       }
+       
+       
+
+       @Override
+       public Point getOptimumPoint() {
+               return current;
+       }
+       
+       
+       @Override
+       public double getOptimumValue() {
+               if (getOptimumPoint() == null) {
+                       return Double.NaN;
+               }
+               return functionExecutor.getValue(getOptimumPoint());
+       }
+       
+       @Override
+       public FunctionCache getFunctionCache() {
+               return functionExecutor;
+       }
+       
+       @Override
+       public void setFunctionCache(FunctionCache functionCache) {
+               if (!(functionCache instanceof ParallelFunctionCache)) {
+                       throw new IllegalArgumentException("Function cache needs to be a ParallelFunctionCache: " + functionCache);
+               }
+               this.functionExecutor = (ParallelFunctionCache) functionCache;
+       }
+       
+       @Override
+       public String getStatistics() {
+               return String.format("Guess hit rate %d/%d = %.3f", guessSuccess, guessSuccess + guessFailure,
+                               ((double) guessSuccess) / (guessSuccess + guessFailure));
+       }
+       
+       @Override
+       public void resetStatistics() {
+               guessSuccess = 0;
+               guessFailure = 0;
+       }
+       
+}
index 1a8f3ff115ca8732c3b4b2b5ee5040709aa4dd1e..2acdb04913666d99e554d2a7ad275c485cbeeb9d 100644 (file)
@@ -29,7 +29,7 @@ public interface OptimizableParameter {
         * @return                              the parameter value (any double value)
         * @throws OptimizationException        if an error occurs preventing the optimization from continuing
         */
-       public double computeValue(Simulation simulation) throws OptimizationException;
+       public double computeValue(Simulation simulation) throws OptimizationException, InterruptedException;
        
        
        /**
index 431e9ae5fa6fe988aca0bb63f0fa2224a0213069..124fe65bf6c5d18d459c961f4ce0dd8e202e3f12 100644 (file)
@@ -7,8 +7,10 @@ import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationCancelledException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.simulation.listeners.system.InterruptListener;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -28,10 +30,10 @@ public class GroundHitVelocityParameter implements OptimizableParameter {
        }
        
        @Override
-       public double computeValue(Simulation simulation) throws OptimizationException {
+       public double computeValue(Simulation simulation) throws OptimizationException, InterruptedException {
                try {
                        log.debug("Running simulation to evaluate ground hit speed");
-                       simulation.simulate();
+                       simulation.simulate(new InterruptListener());
                        double value = simulation.getSimulatedData().getBranch(0).getLast(FlightDataType.TYPE_VELOCITY_TOTAL);
                        log.debug("Ground hit speed was " + value);
                        return value;
@@ -41,6 +43,8 @@ public class GroundHitVelocityParameter implements OptimizableParameter {
                } catch (SimulationLaunchException e) {
                        // Other launch exceptions result in zero altitude
                        return Double.NaN;
+               } catch (SimulationCancelledException e) {
+                       throw (InterruptedException) new InterruptedException("Optimization was interrupted").initCause(e);
                } catch (SimulationException e) {
                        // Other exceptions fail
                        throw new OptimizationException(e);
index 4d6f5713e63c13f5ced090031bfc582b544aa5bb..c5dc0061be63812b71d07ab61e1cba96924ad7cd 100644 (file)
@@ -7,8 +7,10 @@ import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationCancelledException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.simulation.listeners.system.InterruptListener;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -28,10 +30,10 @@ public class LandingDistanceParameter implements OptimizableParameter {
        }
        
        @Override
-       public double computeValue(Simulation simulation) throws OptimizationException {
+       public double computeValue(Simulation simulation) throws OptimizationException, InterruptedException {
                try {
                        log.debug("Running simulation to evaluate rocket landing distance");
-                       simulation.simulate();
+                       simulation.simulate(new InterruptListener());
                        double value = simulation.getSimulatedData().getBranch(0).getLast(FlightDataType.TYPE_POSITION_XY);
                        log.debug("Landing distance was " + value);
                        return value;
@@ -41,6 +43,8 @@ public class LandingDistanceParameter implements OptimizableParameter {
                } catch (SimulationLaunchException e) {
                        // Other launch exceptions result in zero altitude
                        return Double.NaN;
+               } catch (SimulationCancelledException e) {
+                       throw (InterruptedException) new InterruptedException("Optimization was interrupted").initCause(e);
                } catch (SimulationException e) {
                        // Other exceptions fail
                        throw new OptimizationException(e);
index 51f73d82896a0ca7f9b8eb637437a005da5f3f18..7b007aa2642578ec864c8429961c55de548e2eed 100644 (file)
@@ -7,9 +7,11 @@ import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationCancelledException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
 import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.simulation.listeners.system.InterruptListener;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -29,10 +31,10 @@ public class MaximumAccelerationParameter implements OptimizableParameter {
        }
        
        @Override
-       public double computeValue(Simulation simulation) throws OptimizationException {
+       public double computeValue(Simulation simulation) throws OptimizationException, InterruptedException {
                try {
                        log.debug("Running simulation to evaluate maximum acceleration");
-                       simulation.simulate(new ApogeeEndListener());
+                       simulation.simulate(new ApogeeEndListener(), new InterruptListener());
                        double value = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ACCELERATION_TOTAL);
                        log.debug("Maximum acceleration was " + value);
                        return value;
@@ -42,6 +44,8 @@ public class MaximumAccelerationParameter implements OptimizableParameter {
                } catch (SimulationLaunchException e) {
                        // Other launch exceptions result in zero velocity
                        return Double.NaN;
+               } catch (SimulationCancelledException e) {
+                       throw (InterruptedException) new InterruptedException("Optimization was interrupted").initCause(e);
                } catch (SimulationException e) {
                        // Other exceptions fail
                        throw new OptimizationException(e);
index 248d1cf9cf781d2dba7bca2812bbe278b9fad3e4..7498ed118665f15884479f545f36754678447332 100644 (file)
@@ -7,9 +7,11 @@ import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationCancelledException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
 import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.simulation.listeners.system.InterruptListener;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -29,10 +31,10 @@ public class MaximumAltitudeParameter implements OptimizableParameter {
        }
        
        @Override
-       public double computeValue(Simulation simulation) throws OptimizationException {
+       public double computeValue(Simulation simulation) throws OptimizationException, InterruptedException {
                try {
                        log.debug("Running simulation to evaluate apogee altitude");
-                       simulation.simulate(new ApogeeEndListener());
+                       simulation.simulate(new ApogeeEndListener(), new InterruptListener());
                        log.debug("Maximum altitude was " + simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE));
                        return simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE);
                } catch (MotorIgnitionException e) {
@@ -41,6 +43,8 @@ public class MaximumAltitudeParameter implements OptimizableParameter {
                } catch (SimulationLaunchException e) {
                        // Other launch exceptions result in zero altitude
                        return 0.0;
+               } catch (SimulationCancelledException e) {
+                       throw (InterruptedException) new InterruptedException("Optimization was interrupted").initCause(e);
                } catch (SimulationException e) {
                        // Other exceptions fail
                        throw new OptimizationException(e);
index 58c7a278e9d92953151124aea862df19b76e7429..229df474ad3ad5be10c0a3c587685e0201ac94c2 100644 (file)
@@ -7,9 +7,11 @@ import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationCancelledException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
 import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.simulation.listeners.system.InterruptListener;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -29,10 +31,10 @@ public class MaximumVelocityParameter implements OptimizableParameter {
        }
        
        @Override
-       public double computeValue(Simulation simulation) throws OptimizationException {
+       public double computeValue(Simulation simulation) throws OptimizationException, InterruptedException {
                try {
                        log.debug("Running simulation to evaluate maximum velocity");
-                       simulation.simulate(new ApogeeEndListener());
+                       simulation.simulate(new ApogeeEndListener(), new InterruptListener());
                        double value = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_VELOCITY_TOTAL);
                        log.debug("Maximum velocity was " + value);
                        return value;
@@ -42,6 +44,8 @@ public class MaximumVelocityParameter implements OptimizableParameter {
                } catch (SimulationLaunchException e) {
                        // Other launch exceptions result in zero velocity
                        return Double.NaN;
+               } catch (SimulationCancelledException e) {
+                       throw (InterruptedException) new InterruptedException("Optimization was interrupted").initCause(e);
                } catch (SimulationException e) {
                        // Other exceptions fail
                        throw new OptimizationException(e);
index 75b18943ad1d53485f8f7c5073c6ac4c790ccc36..dc2e105d63743f7c9c10246af6fc65d7f706b2dc 100644 (file)
@@ -152,6 +152,18 @@ public class MathUtil {
                }
        }
        
+       
+
+       /**
+        * Compute the minimum of three values.  This is performed by direct comparison. 
+        * However, if one of the values is NaN and the other is not, the non-NaN value is
+        * returned.
+        */
+       public static double min(double w, double x, double y, double z) {
+               return min(min(w, x), min(y, z));
+       }
+       
+       
        /**
         * Compute the maximum of three values.  This is performed by direct comparison. 
         * However, if one of the values is NaN and the other is not, the non-NaN value is
diff --git a/test/net/sf/openrocket/l10n/TestResourceBundleTranslator.java b/test/net/sf/openrocket/l10n/TestResourceBundleTranslator.java
new file mode 100644 (file)
index 0000000..db7dbfc
--- /dev/null
@@ -0,0 +1,34 @@
+package net.sf.openrocket.l10n;
+
+import static org.junit.Assert.*;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+
+import org.junit.Test;
+
+public class TestResourceBundleTranslator {
+       
+       @Test
+       public void testSuccessfulUS() {
+               ResourceBundleTranslator trans = new ResourceBundleTranslator("l10n.messages", Locale.US);
+               assertEquals("messages.properties", trans.get("debug.currentFile"));
+       }
+       
+       @Test
+       public void testSuccessfulFR() {
+               ResourceBundleTranslator trans = new ResourceBundleTranslator("l10n.messages", Locale.FRENCH);
+               assertEquals("messages_fr.properties", trans.get("debug.currentFile"));
+       }
+       
+       @Test
+       public void testFailure() {
+               ResourceBundleTranslator trans = new ResourceBundleTranslator("l10n.messages", Locale.US);
+               try {
+                       fail("Returned: " + trans.get("missing"));
+               } catch (MissingResourceException e) {
+                       // Expected
+               }
+       }
+       
+}
index a794a090797fdb7943fd546885caa490cbc51471..8b79aeca08536ad0b0002a6b8e8689fcd4faaaf5 100644 (file)
@@ -98,6 +98,16 @@ public class MathUtilTest {
                assertEquals(2.0, MathUtil.max(2.0, NaN, 1.0), 0);
                assertEquals(2.0, MathUtil.max(1.0, 2.0, NaN), 0);
                assertEquals(2.0, MathUtil.max(NaN, 2.0, 1.0), 0);
+               
+               assertEquals(1.0, MathUtil.min(1.0, 2.0, 3.0, 4.0), 0);
+               assertEquals(1.0, MathUtil.min(1.0, NaN, NaN, NaN), 0);
+               assertEquals(1.0, MathUtil.min(NaN, 1.0, NaN, NaN), 0);
+               assertEquals(1.0, MathUtil.min(NaN, NaN, 1.0, NaN), 0);
+               assertEquals(1.0, MathUtil.min(2.0, NaN, 1.0, NaN), 0);
+               assertEquals(1.0, MathUtil.min(2.0, NaN, NaN, 1.0), 0);
+               assertEquals(1.0, MathUtil.min(1.0, 2.0, NaN, 3.0), 0);
+               assertEquals(1.0, MathUtil.min(NaN, 2.0, 3.0, 1.0), 0);
+               
        }
        
        @Test