From: plaa Date: Tue, 9 Aug 2011 19:57:15 +0000 (+0000) Subject: optimization updates X-Git-Tag: upstream/1.1.7^2~5 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=aba2f5f25981c5b24ca4d9c6a63ab381482f9e01;p=debian%2Fopenrocket optimization updates git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@148 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/ChangeLog b/ChangeLog index 3b3ed8f1..71e4ddcf 100644 --- 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 diff --git a/build.xml b/build.xml index 84462ba7..c716a528 100644 --- a/build.xml +++ b/build.xml @@ -89,7 +89,7 @@ - + diff --git a/src/net/sf/openrocket/gui/components/DoubleCellEditor.java b/src/net/sf/openrocket/gui/components/DoubleCellEditor.java index 6ff86822..1602350a 100644 --- a/src/net/sf/openrocket/gui/components/DoubleCellEditor.java +++ b/src/net/sf/openrocket/gui/components/DoubleCellEditor.java @@ -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(); diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java b/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java index fe0e3774..f60c720d 100644 --- a/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java @@ -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); } } } diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java index b7d18445..eda1f9ae 100644 --- a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java +++ b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java @@ -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); + } } diff --git a/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java b/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java index 64b88ff1..67154da5 100644 --- a/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java +++ b/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java @@ -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 iterator = futureMap.keySet().iterator(); + while (iterator.hasNext()) { + Point point = iterator.next(); + Future 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 { } } + } diff --git a/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java b/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java index 05dcba23..1c8a35f6 100644 --- a/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java +++ b/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java @@ -68,4 +68,10 @@ public interface ParallelFunctionCache extends FunctionCache { * @return true if the point has been computed anyway, false if not. */ public boolean abort(Point point); + + + /** + * Abort the computation of all still unexecuted points. + */ + public void abortAll(); } diff --git a/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java b/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java index 1f15800f..76d2f301 100644 --- a/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java +++ b/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java @@ -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 index 00000000..853ffbf5 --- /dev/null +++ b/src/net/sf/openrocket/optimization/general/onedim/GoldenSectionSearchOptimizer.java @@ -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) + *

+ * This implementation attempts to guess future evaluations and computes them in parallel + * with the next point. + *

+ * 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; + } + +} diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java index 1a8f3ff1..2acdb049 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java @@ -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; /** diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java index 431e9ae5..124fe65b 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java @@ -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); diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java index 4d6f5713..c5dc0061 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java @@ -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); diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java index 51f73d82..7b007aa2 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java @@ -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); diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java index 248d1cf9..7498ed11 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java @@ -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); diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java index 58c7a278..229df474 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java @@ -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); diff --git a/src/net/sf/openrocket/util/MathUtil.java b/src/net/sf/openrocket/util/MathUtil.java index 75b18943..dc2e105d 100644 --- a/src/net/sf/openrocket/util/MathUtil.java +++ b/src/net/sf/openrocket/util/MathUtil.java @@ -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 index 00000000..db7dbfc7 --- /dev/null +++ b/test/net/sf/openrocket/l10n/TestResourceBundleTranslator.java @@ -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 + } + } + +} diff --git a/test/net/sf/openrocket/util/MathUtilTest.java b/test/net/sf/openrocket/util/MathUtilTest.java index a794a090..8b79aeca 100644 --- a/test/net/sf/openrocket/util/MathUtilTest.java +++ b/test/net/sf/openrocket/util/MathUtilTest.java @@ -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