+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
<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}"/>
package net.sf.openrocket.gui.components;
import java.awt.Component;
+import java.text.ParseException;
import javax.swing.AbstractCellEditor;
import javax.swing.JSpinner;
}
+ @Override
+ public boolean stopCellEditing() {
+ try {
+ editor.commitEdit();
+ } catch (ParseException e) {
+ // Ignore
+ }
+ return super.stopCellEditing();
+ }
+
+
@Override
public Object getCellEditorValue() {
return model.getValue();
import com.itextpdf.text.Font;
+// FIXME: Override to zero mass produces NaN in simulation
+
/**
* General rocket optimization dialog.
*
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);
}
}
}
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;
cache = new ParallelExecutorCache(1);
cache.setFunction(function);
- optimizer = new MultidirectionalSearchOptimizer(cache);
+ if (modifiers.length == 1) {
+ optimizer = new GoldenSectionSearchOptimizer(cache);
+ } else {
+ optimizer = new MultidirectionalSearchOptimizer(cache);
+ }
}
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;
}
+ @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)) {
}
}
+
}
* @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();
}
log.info("Finishing optimization at point " + simplex.get(0) + " value = " +
functionExecutor.getValue(simplex.get(0)));
+ log.info("Optimization statistics: " + getStatistics());
}
--- /dev/null
+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;
+ }
+
+}
* @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;
/**
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;
}
@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;
} 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);
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;
}
@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;
} 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);
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;
}
@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;
} 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);
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;
}
@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) {
} 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);
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;
}
@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;
} 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);
}
}
+
+
+ /**
+ * 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
--- /dev/null
+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
+ }
+ }
+
+}
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