optimization updates

This commit is contained in:
Sampo Niskanen 2011-08-09 19:57:15 +00:00
parent 9a8e702df2
commit 2639d13391
18 changed files with 425 additions and 14 deletions

View File

@ -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

View File

@ -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}"/>

View 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();

View 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);
}
}
}

View 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);
}
}

View 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 {
}
}
}

View 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();
}

View 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());
}

View File

@ -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;
}
}

View 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;
/**

View 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);

View 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);

View 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);

View 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);

View 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);

View 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

View File

@ -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
}
}
}

View 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