+2011-08-07 Sampo Niskanen
+
+ * Optimization implementation
+
+2011-07-29 Sampo Niskanen
+
+ * [BUG] NaN in transition radius calculation
+
2011-07-22 Sampo Niskanen
* Released version 1.1.6.
! Labels used in buttons of dialog windows
+# TODO: Rename these to "btn.xxx"
button.ok = OK
button.cancel = Cancel
button.close = Close
SimExpPan.Col.Variable = Variable
SimExpPan.Col.Unit = Unit
+
+CsvOptionPanel.separator.space = SPACE
+CsvOptionPanel.separator.tab = TAB
+
+
+
! MotorPlot
MotorPlot.title.Motorplot = Motor plot
MotorPlot.but.Select = Select
PlotDialog.title.Flightdataplot = Flight data plot
PlotDialog.Chart.Simulatedflight = Simulated flight
PlotDialog.CheckBox.Showdatapoints = Show data points
-PlotDialog.lbl.Chart = Click+drag down+right to zoom in, up+left to zoom out
+PlotDialog.lbl.Chart = Click and drag down+right to zoom in, up+left to zoom out
! "main" prefix is used for the main application dialog
main.menu.analyze.desc = Rocket analysis
main.menu.analyze.componentAnalysis = Component analysis
main.menu.analyze.componentAnalysis.desc = Analyze the rocket components separately
+main.menu.analyze.optimization = Rocket optimization
+main.menu.analyze.optimization.desc = General rocket design optimization
main.menu.help = Help
main.menu.help.desc = Information about OpenRocket
! Modifiers
-optimization.modifier.nosecone.length = Nose cone length
-optimization.modifier.nosecone.diameter = Nose cone diameter
-optimization.modifier.nosecone.thickness = Nose cone thickness
-
-optimization.modifier.transition.length = Transition length
-optimization.modifier.transition.forediameter = Transition fore diameter
-optimization.modifier.transition.aftdiameter = Transition aft diameter
-optimization.modifier.transition.thickness = Transition thickness
-
-optimization.modifier.bodytube.length = Body tube length
-optimization.modifier.bodytube.outerDiameter = Body tube outer diameter
-optimization.modifier.bodytube.innerDiameter = Body tube inner diameter
-optimization.modifier.bodytube.thickness = Body tube thickness
-
+optimization.modifier.nosecone.length = Length
+optimization.modifier.nosecone.length.desc = Optimize the nose cone length.
+optimization.modifier.nosecone.diameter = Diameter
+optimization.modifier.nosecone.diameter.desc = Optimize the nose cone base diameter.
+optimization.modifier.nosecone.thickness = Thickness
+optimization.modifier.nosecone.thickness.desc = Optimize the nose cone wall thickness.
+optimization.modifier.nosecone.shapeparameter = Shape parameter
+optimization.modifier.nosecone.shapeparameter.desc = Optimize the nose cone shape parameter.
+
+optimization.modifier.transition.length = Length
+optimization.modifier.transition.length.desc = Optimize the transition length.
+optimization.modifier.transition.forediameter = Fore diameter
+optimization.modifier.transition.forediameter.desc = Optimize the transition fore diameter.
+optimization.modifier.transition.aftdiameter = Aft diameter
+optimization.modifier.transition.aftdiameter.desc = Optimize the transition aft diameter.
+optimization.modifier.transition.thickness = Thickness
+optimization.modifier.transition.thickness.desc = Optimize the transition wall thickness.
+optimization.modifier.transition.shapeparameter = Shape parameter
+optimization.modifier.transition.shapeparameter.desc = Optimize the transition shape parameter.
+
+optimization.modifier.bodytube.length = Length
+optimization.modifier.bodytube.length.desc = Optimize the body tube length.
+optimization.modifier.bodytube.outerDiameter = Outer diameter
+optimization.modifier.bodytube.outerDiameter.desc = Optimize the body tube outer diameter while maintaining the wall thickness.
+optimization.modifier.bodytube.thickness = Thickness
+optimization.modifier.bodytube.thickness.desc = Optimize the body tube wall thickness.
+
+optimization.modifier.trapezoidfinset.rootChord = Root chord
+optimization.modifier.trapezoidfinset.rootChord.desc = Optimize the root chord length of the fin set (length of fin at the rocket body).
+optimization.modifier.trapezoidfinset.tipChord = Tip chord
+optimization.modifier.trapezoidfinset.tipChord.desc = Optimize the tip chord length of the fin set (length of fin at outer edge).
+optimization.modifier.trapezoidfinset.sweep = Sweep
+optimization.modifier.trapezoidfinset.sweep.desc = Optimize the sweep of the fin set (distance that the leading edge sweeps backwards).
+optimization.modifier.trapezoidfinset.height = Height
+optimization.modifier.trapezoidfinset.height.desc = Optimize the height (semi-span) of the fin set.
+
+optimization.modifier.ellipticalfinset.length = Root chord
+optimization.modifier.ellipticalfinset.length.desc = Optimize the root chord length of the fin set.
+optimization.modifier.ellipticalfinset.height = Height
+optimization.modifier.ellipticalfinset.height = Optimize the height (semi-span) of the fin set.
+
+optimization.modifier.finset.cant = Cant angle
+optimization.modifier.finset.cant.desc = Optimize the cant angle of the fin set.
+optimization.modifier.finset.position = Position
+optimization.modifier.finset.position.desc = Optimize the fin set position along the rocket body.
+
+optimization.modifier.launchlug.length = Length
+optimization.modifier.launchlug.length.desc = Optimize the launch lug length.
+optimization.modifier.launchlug.outerDiameter = Outer diameter
+optimization.modifier.launchlug.outerDiameter.desc = Optimize the outer diameter of the launch lug.
+optimization.modifier.launchlug.thickness = Thickness
+optimization.modifier.launchlug.thickness.desc = Optimize the launch lug thickness while keeping the outer diameter constant.
+optimization.modifier.launchlug.position = Position
+optimization.modifier.launchlug.position.desc = Optimize the launch lug position along the rocket body.
+
+
+optimization.modifier.internalcomponent.position = Position
+optimization.modifier.internalcomponent.position.desc = Optimize the position of the component relative to the parent component.
+
+optimization.modifier.masscomponent.mass = Mass
+optimization.modifier.masscomponent.mass.desc = Optimize the mass of the mass component.
+
+optimization.modifier.parachute.diameter = Diameter
+optimization.modifier.parachute.diameter.desc = Optimize the parachute canopy diameter.
+optimization.modifier.parachute.coefficient = Drag coefficient
+optimization.modifier.parachute.coefficient.desc = Optimize the drag coefficient of the parachute. Typical parachutes have a drag coefficient of about 0.8.
+
+optimization.modifier.streamer.length = Length
+optimization.modifier.streamer.length.desc = Optimize the length of the streamer.
+optimization.modifier.streamer.width = Width
+optimization.modifier.streamer.width.desc = Optimize the width of the streamer.
+optimization.modifier.streamer.aspectRatio = Aspect ratio
+optimization.modifier.streamer.aspectRatio.desc = Optimize the aspect ratio of the streamer (length/width). You should NOT select streamer length or width at the same time with the aspect ratio.
+optimization.modifier.streamer.coefficient = Drag coefficient
+optimization.modifier.streamer.coefficient.desc = Optimize the drag coefficient of the streamer.
+
+optimization.modifier.recoverydevice.deployDelay = Deployment delay
+optimization.modifier.recoverydevice.deployDelay.desc = Optimize the deployment delay of the recovery device.
+optimization.modifier.recoverydevice.deployAltitude = Deployment altitude
+optimization.modifier.recoverydevice.deployAltitude.desc = Optimize the deployment altitude of the recovery device.
+
+optimization.modifier.rocketcomponent.overrideMass = Override mass
+optimization.modifier.rocketcomponent.overrideMass.desc = Optimize the overridden mass of the component.
+optimization.modifier.rocketcomponent.overrideCG = Override CG
+optimization.modifier.rocketcomponent.overrideCG.desc = Optimize the overridden center of gravity of the component.
+
+optimization.modifier.motormount.overhang = Motor overhang
+optimization.modifier.motormount.overhang.desc = Optimize the motor overhang.
+optimization.modifier.motormount.delay = Motor ignition delay
+optimization.modifier.motormount.delay.desc = Optimize the motor ignition delay.
+
+
+
+
+! General rocket design optimization dialog
+
+GeneralOptimizationDialog.title = Rocket optimization
+GeneralOptimizationDialog.goal.maximize = Maximize value
+GeneralOptimizationDialog.goal.minimize = Minimize value
+GeneralOptimizationDialog.goal.seek = Seek value of
+GeneralOptimizationDialog.btn.start = Start optimization
+GeneralOptimizationDialog.btn.stop = Stop optimization
+GeneralOptimizationDialog.lbl.paramsToOptimize = Parameters to optimize:
+GeneralOptimizationDialog.btn.add = Add
+GeneralOptimizationDialog.btn.add.ttip = Add the selected parameter to the optimization
+GeneralOptimizationDialog.btn.remove = Remove
+GeneralOptimizationDialog.btn.remove.ttip = Remove the selected parameter from the optimization
+GeneralOptimizationDialog.btn.removeAll = Remove all
+GeneralOptimizationDialog.btn.removeAll.ttip = Remove all parameters from the optimization
+GeneralOptimizationDialog.lbl.availableParams = Available parameters:
+GeneralOptimizationDialog.lbl.optimizationOpts = Optimization options
+GeneralOptimizationDialog.lbl.optimizeSim = Optimize simulation:
+GeneralOptimizationDialog.lbl.optimizeSim.ttip = Select which simulation to optimize
+GeneralOptimizationDialog.lbl.optimizeValue = Optimized value:
+GeneralOptimizationDialog.lbl.optimizeValue.ttip = Select what value is to be optimized
+GeneralOptimizationDialog.lbl.optimizeGoal = Optimization goal:
+GeneralOptimizationDialog.lbl.optimizeGoal.ttip = Select the goal of the optimization
+GeneralOptimizationDialog.lbl.optimizeGoalValue.ttip = Custom value to seek
+GeneralOptimizationDialog.lbl.requireStability = Required stability
+GeneralOptimizationDialog.lbl.requireMinStability = Minimum stability:
+GeneralOptimizationDialog.lbl.requireMinStability.ttip = Require a minimum static stability margin for the design
+GeneralOptimizationDialog.lbl.requireMaxStability = Maximum stability:
+GeneralOptimizationDialog.lbl.requireMaxStability.ttip = Require a maximum static stability margin for the design
+GeneralOptimizationDialog.status.bestValue = Best value:
+GeneralOptimizationDialog.status.bestValue.ttip = Best optimization value found so far.
+GeneralOptimizationDialog.status.stepCount = Step count:
+GeneralOptimizationDialog.status.stepCount.ttip = Number of optimization steps that have been performed.
+GeneralOptimizationDialog.status.evalCount = Evaluations:
+GeneralOptimizationDialog.status.evalCount.ttip = Total number of function evaluations (simulations) that have been performed.
+GeneralOptimizationDialog.status.stepSize = Step size:
+GeneralOptimizationDialog.status.stepSize.ttip = Current optimization step size (relative to the optimization parameter ranges)
+GeneralOptimizationDialog.btn.plotPath = Plot path
+GeneralOptimizationDialog.btn.plotPath.ttip = Plot the optimization path (one and two dimensional optimization only)
+GeneralOptimizationDialog.btn.save = Save path
+GeneralOptimizationDialog.btn.save.ttip = Save the results of the function evaluations (simulations) as a CSV file.
+GeneralOptimizationDialog.btn.apply = Apply optimization
+GeneralOptimizationDialog.btn.apply.ttip = Apply the optimization results to the rocket design
+GeneralOptimizationDialog.btn.reset = Reset
+GeneralOptimizationDialog.btn.reset.ttip = Reset the rocket design to the current rocket design
+GeneralOptimizationDialog.btn.close = Close
+GeneralOptimizationDialog.btn.close.ttip = Close the dialog without modifying the rocket design
+GeneralOptimizationDialog.error.selectParams.text = First select some parameters to optimize from the available parameters.
+GeneralOptimizationDialog.error.selectParams.title = Select optimization parameters
+GeneralOptimizationDialog.error.optimizationFailure.text = The optimization failed to run:
+GeneralOptimizationDialog.error.optimizationFailure.title = Optimization failed
+GeneralOptimizationDialog.undoText = Apply optimization
+GeneralOptimizationDialog.basicSimulationName = Basic simulation
+GeneralOptimizationDialog.noSimulationName = No simulation
+GeneralOptimizationDialog.table.col.parameter = Parameter
+GeneralOptimizationDialog.table.col.current = Current
+GeneralOptimizationDialog.table.col.min = Minimum
+GeneralOptimizationDialog.table.col.max = Maximum
+GeneralOptimizationDialog.export.header = Include header line
+GeneralOptimizationDialog.export.header.ttip = Include a header line as the first line containing the field descriptions.
+GeneralOptimizationDialog.export.stability = Stability
+
+
+! Dialog for plotting optimization results
+OptimizationPlotDialog.title = Optimization results
+OptimizationPlotDialog.lbl.zoomInstructions = Click and drag down+right to zoom in, up+left to zoom out
+OptimizationPlotDialog.plot1d.title = Optimization result
+OptimizationPlotDialog.plot1d.series = Optimization result
+OptimizationPlotDialog.plot2d.title = Optimization path
+OptimizationPlotDialog.plot2d.path = Optimization path
+OptimizationPlotDialog.plot2d.evals = Evaluations
+OptimizationPlotDialog.plot.ttip.stability = Stability:
+
+! Optimization parameters
+MaximumAltitudeParameter.name = Apogee altitude
+MaximumVelocityParameter.name = Maximum velocity
+MaximumAccelerationParameter.name = Maximum acceleration
+StabilityParameter.name = Stability
+GroundHitVelocityParameter.name = Ground hit speed
+LandingDistanceParameter.name = Landing distance
import net.sf.openrocket.aerodynamics.barrowman.RocketComponentCalc;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.PolyInterpolator;
// System.out.printf("constant Cf=%f ",Cf);
} else if (Re < 5.39e5) {
// Fully laminar
- Cf = 1.328 / Math.sqrt(Re);
+ Cf = 1.328 / MathUtil.safeSqrt(Re);
// System.out.printf("basic Cf=%f ",Cf);
} else {
// Transitional
* Sqrt(1 - M^2) for M<1
* Sqrt(M^2 - 1) for M>1
*/
- private double beta = Math.sqrt(1 - mach * mach);
+ private double beta = MathUtil.safeSqrt(1 - mach * mach);
/** Current roll rate. */
*/
public void setRefArea(double area) {
refArea = area;
- refLength = Math.sqrt(area / Math.PI) * 2;
+ refLength = MathUtil.safeSqrt(area / Math.PI) * 2;
fireChangeEvent();
}
this.mach = mach;
if (mach < 1)
- this.beta = Math.sqrt(1 - mach * mach);
+ this.beta = MathUtil.safeSqrt(1 - mach * mach);
else
- this.beta = Math.sqrt(mach * mach - 1);
+ this.beta = MathUtil.safeSqrt(mach * mach - 1);
fireChangeEvent();
}
*
* @return the number of times this object has been modified since instantiation.
*/
+ @Override
public int getModID() {
return modID + modIDadd + this.atmosphericConditions.getModID();
}
package net.sf.openrocket.aerodynamics.barrowman;
-import static java.lang.Math.*;
+import static java.lang.Math.pow;
import static net.sf.openrocket.util.MathUtil.pow2;
import java.util.Arrays;
double[] k3 = new double[n];
for (int i = 0; i < n; i++) {
double M = CNA_SUPERSONIC + i * 0.1;
- double beta = sqrt(M * M - 1);
+ double beta = MathUtil.safeSqrt(M * M - 1);
x[i] = M;
k1[i] = 2.0 / beta;
k2[i] = ((GAMMA + 1) * pow(M, 4) - 4 * pow2(beta)) / (4 * pow(beta, 4));
// Subsonic case
if (mach <= CNA_SUBSONIC) {
- return 2 * Math.PI * pow2(span) / (1 + sqrt(1 + (1 - pow2(mach)) *
+ return 2 * Math.PI * pow2(span) / (1 + MathUtil.safeSqrt(1 + (1 - pow2(mach)) *
pow2(pow2(span) / (finArea * cosGamma)))) / ref;
}
double subV, superV;
double subD, superD;
- double sq = sqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
+ double sq = MathUtil.safeSqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
subV = 2 * Math.PI * pow2(span) / ref / (1 + sq);
subD = 2 * mach * Math.PI * pow(span, 6) / (pow2(finArea * cosGamma) * ref *
sq * pow2(1 + sq));
p = (param - 0.5) * 4;
} else {
int1 = x34Interpolator;
- int2 = calculateOgiveNoseInterpolator(0, 1 / Math.sqrt(1 + 4 * pow2(fineness)));
+ int2 = calculateOgiveNoseInterpolator(0, 1 / MathUtil.safeSqrt(1 + 4 * pow2(fineness)));
p = (param - 0.75) * 4;
}
break;
case PARABOLIC:
if (param <= 0.5) {
- int1 = calculateOgiveNoseInterpolator(0, 1 / Math.sqrt(1 + 4 * pow2(fineness)));
+ int1 = calculateOgiveNoseInterpolator(0, 1 / MathUtil.safeSqrt(1 + 4 * pow2(fineness)));
int2 = parabolic12Interpolator;
p = param * 2;
} else if (param <= 0.75) {
// Above M = 1.3 use direct formula
for (double m = 1.32; m < 4; m += 0.02) {
- interpolator.addPoint(m, mul * (2.1 * pow2(sinphi) + 0.5 * sinphi / Math.sqrt(m * m - 1)));
+ interpolator.addPoint(m, mul * (2.1 * pow2(sinphi) + 0.5 * sinphi / MathUtil.safeSqrt(m * m - 1)));
}
return interpolator;
/**
* Class describing an entire OpenRocket document, including a rocket and
- * simulations. This class also handles undo/redo operations for the rocket structure.
+ * simulations. The document contains:
+ * <p>
+ * - the rocket definition
+ * - a default Configuration
+ * - Simulation instances
+ * - the stored file and file save information
+ * - undo/redo information
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class OpenRocketDocument implements ComponentChangeListener {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
/**
* The minimum number of undo levels that are stored.
*/
/** Whether an undo error has already been reported to the user */
private static boolean undoErrorReported = false;
+
+
private final Rocket rocket;
private final Configuration configuration;
}
}
+
+
+ /**
+ * Return a copy of this document. The rocket is copied with original ID's, the default
+ * motor configuration ID is maintained and the simulations are copied to the new rocket.
+ * No undo/redo information or file storage information is maintained.
+ *
+ * @return a copy of this document.
+ */
+ public OpenRocketDocument copy() {
+ Rocket rocketCopy = rocket.copyWithOriginalID();
+ OpenRocketDocument documentCopy = new OpenRocketDocument(rocketCopy);
+ documentCopy.getDefaultConfiguration().setMotorConfigurationID(configuration.getMotorConfigurationID());
+ for (Simulation s : simulations) {
+ documentCopy.addSimulation(s.duplicateSimulation(rocketCopy));
+ }
+ return documentCopy;
+ }
+
+
+
/////// Listeners
public void addDocumentChangeListener(DocumentChangeListener listener) {
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.simulation.BasicEventSimulationEngine;
import net.sf.openrocket.simulation.FlightData;
-import net.sf.openrocket.simulation.GUISimulationConditions;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationEngine;
+import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.SimulationStepper;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationListenerException;
/** The conditions to use */
// TODO: HIGH: Change to use actual conditions class??
- private GUISimulationConditions conditions;
+ private SimulationOptions options;
private ArrayList<String> simulationListeners = new ArrayList<String>();
/** The conditions actually used in the previous simulation, or null */
- private GUISimulationConditions simulatedConditions = null;
+ private SimulationOptions simulatedConditions = null;
private String simulatedMotors = null;
private FlightData simulatedData = null;
private int simulatedRocketID = -1;
this.rocket = rocket;
this.status = Status.NOT_SIMULATED;
- conditions = new GUISimulationConditions(rocket);
- conditions.setMotorConfigurationID(
+ options = new SimulationOptions(rocket);
+ options.setMotorConfigurationID(
rocket.getDefaultConfiguration().getMotorConfigurationID());
- conditions.addChangeListener(new ConditionListener());
+ options.addChangeListener(new ConditionListener());
}
- public Simulation(Rocket rocket, Status status, String name, GUISimulationConditions conditions,
+ public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
List<String> listeners, FlightData data) {
if (rocket == null)
throw new IllegalArgumentException("status cannot be null");
if (name == null)
throw new IllegalArgumentException("name cannot be null");
- if (conditions == null)
- throw new IllegalArgumentException("conditions cannot be null");
+ if (options == null)
+ throw new IllegalArgumentException("options cannot be null");
this.rocket = rocket;
this.name = name;
- this.conditions = conditions;
- conditions.addChangeListener(new ConditionListener());
+ this.options = options;
+ options.addChangeListener(new ConditionListener());
if (listeners != null) {
this.simulationListeners.addAll(listeners);
if (data != null && this.status != Status.NOT_SIMULATED) {
simulatedData = data;
if (this.status == Status.LOADED) {
- simulatedConditions = conditions.clone();
+ simulatedConditions = options.clone();
simulatedRocketID = rocket.getModID();
}
}
public Configuration getConfiguration() {
mutex.verify();
Configuration c = new Configuration(rocket);
- c.setMotorConfigurationID(conditions.getMotorConfigurationID());
+ c.setMotorConfigurationID(options.getMotorConfigurationID());
c.setAllStages();
return c;
}
/**
- * Returns the simulation conditions attached to this simulation. The conditions
+ * Returns the simulation options attached to this simulation. The options
* may be modified freely, and the status of the simulation will change to reflect
* the changes.
*
* @return the simulation conditions.
*/
- public GUISimulationConditions getConditions() {
+ public SimulationOptions getOptions() {
mutex.verify();
- return conditions;
+ return options;
}
if (status == Status.UPTODATE || status == Status.LOADED) {
if (rocket.getFunctionalModID() != simulatedRocketID ||
- !conditions.equals(simulatedConditions))
+ !options.equals(simulatedConditions))
return Status.OUTDATED;
}
throw new IllegalStateException("Cannot instantiate simulator.", e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
- } catch (NullPointerException e) {
- throw new IllegalStateException("Simulator null", e);
}
- SimulationConditions simulationConditions = conditions.toSimulationConditions();
+ SimulationConditions simulationConditions = options.toSimulationConditions();
for (SimulationListener l : additionalListeners) {
simulationConditions.getSimulationListenerList().add(l);
}
log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
// Set simulated info after simulation, will not be set in case of exception
- simulatedConditions = conditions.clone();
+ simulatedConditions = options.clone();
simulatedMotors = getConfiguration().getMotorConfigurationDescription();
simulatedRocketID = rocket.getFunctionalModID();
*
* @return the conditions used in the previous simulation, or <code>null</code>.
*/
- public GUISimulationConditions getSimulatedConditions() {
+ public SimulationOptions getSimulatedConditions() {
mutex.verify();
return simulatedConditions;
}
/**
* Returns a copy of this simulation suitable for cut/copy/paste operations.
+ * The rocket refers to the same instance as the original simulation.
* This excludes any simulated data.
*
* @return a copy of this simulation and its conditions.
copy.mutex = SafetyMutex.newInstance();
copy.status = Status.NOT_SIMULATED;
- copy.conditions = this.conditions.clone();
+ copy.options = this.options.clone();
copy.simulationListeners = this.simulationListeners.clone();
copy.listeners = new ArrayList<ChangeListener>();
copy.simulatedConditions = null;
Simulation copy = new Simulation(newRocket);
copy.name = this.name;
- copy.conditions.copyFrom(this.conditions);
+ copy.options.copyFrom(this.options);
copy.simulationListeners = this.simulationListeners.clone();
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.FlightEvent.Type;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
status = Simulation.Status.OUTDATED;
}
- GUISimulationConditions conditions;
+ SimulationOptions conditions;
if (conditionHandler != null) {
conditions = conditionHandler.getConditions();
} else {
warnings.add("Simulation conditions not defined, using defaults.");
- conditions = new GUISimulationConditions(doc.getRocket());
+ conditions = new SimulationOptions(doc.getRocket());
}
if (name == null)
class SimulationConditionsHandler extends ElementHandler {
- private GUISimulationConditions conditions;
+ private SimulationOptions conditions;
private AtmosphereHandler atmosphereHandler;
public SimulationConditionsHandler(Rocket rocket) {
- conditions = new GUISimulationConditions(rocket);
+ conditions = new SimulationOptions(rocket);
}
- public GUISimulationConditions getConditions() {
+ public SimulationOptions getConditions() {
return conditions;
}
}
- public void storeSettings(GUISimulationConditions cond, WarningSet warnings) {
+ public void storeSettings(SimulationOptions cond, WarningSet warnings) {
if (!Double.isNaN(pressure)) {
cond.setLaunchPressure(pressure);
}
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.MathUtil;
private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
- GUISimulationConditions cond = simulation.getConditions();
+ SimulationOptions cond = simulation.getOptions();
writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) + "\">");
indent++;
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-
public class BooleanModel extends AbstractAction implements ChangeListener, Invalidatable {
private static final LogHelper log = Application.getLogger();
private final ChangeSource source;
private final String valueName;
+ /* Only used when referencing a ChangeSource! */
private final Method getMethod;
private final Method setMethod;
private final Method getEnabled;
+ /* Only used with internal boolean value! */
+ private boolean value;
+
+
private final List<Component> components = new ArrayList<Component>();
private final List<Boolean> componentEnableState = new ArrayList<Boolean>();
private Invalidator invalidator = new Invalidator(this);
+ /**
+ * Construct a BooleanModel that holds the boolean value within itself.
+ *
+ * @param initialValue the initial value of the boolean
+ */
+ public BooleanModel(boolean initialValue) {
+ this.valueName = null;
+ this.source = null;
+ this.getMethod = null;
+ this.setMethod = null;
+ this.getEnabled = null;
+
+ this.value = initialValue;
+
+ oldValue = getValue();
+ oldEnabled = getIsEnabled();
+
+ this.setEnabled(oldEnabled);
+ this.putValue(SELECTED_KEY, oldValue);
+
+ }
+
+ /**
+ * Construct a BooleanModel that references the boolean from a ChangeSource method.
+ *
+ * @param source the boolean source.
+ * @param valueName the name of the getter/setter method (without the get/is/set prefix)
+ */
public BooleanModel(ChangeSource source, String valueName) {
this.source = source;
this.valueName = valueName;
}
public boolean getValue() {
- try {
- return (Boolean) getMethod.invoke(source);
- } catch (IllegalAccessException e) {
- throw new BugException("getMethod execution error for source " + source, e);
- } catch (InvocationTargetException e) {
- throw Reflection.handleWrappedException(e);
+
+ if (getMethod != null) {
+
+ try {
+ return (Boolean) getMethod.invoke(source);
+ } catch (IllegalAccessException e) {
+ throw new BugException("getMethod execution error for source " + source, e);
+ } catch (InvocationTargetException e) {
+ throw Reflection.handleWrappedException(e);
+ }
+
+ } else {
+
+ // Use internal value
+ return value;
+
}
}
public void setValue(boolean b) {
checkState(true);
log.debug("Setting value of " + this + " to " + b);
- try {
- setMethod.invoke(source, new Object[] { b });
- } catch (IllegalAccessException e) {
- throw new BugException("setMethod execution error for source " + source, e);
- } catch (InvocationTargetException e) {
- throw Reflection.handleWrappedException(e);
+
+ if (setMethod != null) {
+ try {
+ setMethod.invoke(source, new Object[] { b });
+ } catch (IllegalAccessException e) {
+ throw new BugException("setMethod execution error for source " + source, e);
+ } catch (InvocationTargetException e) {
+ throw Reflection.handleWrappedException(e);
+ }
+ } else {
+ // Manually fire state change - normally the ChangeSource fires it
+ value = b;
+ stateChanged(null);
}
}
@Override
public String toString() {
if (toString == null) {
- toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
+ if (source != null) {
+ toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
+ } else {
+ toString = "BooleanModel[internal value]";
+ }
}
return toString;
}
// Use quadratic scale
// Further solution of the quadratic equation
// a*x^2 + b*x + c-value == 0
- x = (Math.sqrt(quad1 * quad1 - 4 * quad2 * (quad0 - value)) - quad1) / (2 * quad2);
+ x = (MathUtil.safeSqrt(quad1 * quad1 - 4 * quad2 * (quad0 - value)) - quad1) / (2 * quad2);
}
return (int) (x * MAX);
}
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.Prefs;
+
+/**
+ * A panel that shows options for saving CSV files.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class CsvOptionPanel extends JPanel {
+
+ private static final Translator trans = Application.getTranslator();
+
+ private static final String SPACE = trans.get("CsvOptionPanel.separator.space");
+ private static final String TAB = trans.get("CsvOptionPanel.separator.tab");
+
+ private final String baseClassName;
+
+ private final JComboBox fieldSeparator;
+ private final JCheckBox[] options;
+ private final JComboBox commentCharacter;
+
+ /**
+ * Sole constructor.
+ *
+ * @param includeComments a list of comment inclusion options to provide;
+ * every second item is the option name and every second the tooltip
+ */
+ public CsvOptionPanel(Class<?> baseClass, String... includeComments) {
+ super(new MigLayout("fill, insets 0"));
+
+ this.baseClassName = baseClass.getSimpleName();
+
+ JPanel panel;
+ JLabel label;
+ String tip;
+
+
+ // TODO: HIGH: Rename the translation keys
+
+ // Field separator panel
+ panel = new JPanel(new MigLayout("fill"));
+ panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Fieldsep")));
+
+ label = new JLabel(trans.get("SimExpPan.lbl.Fieldsepstr"));
+ tip = trans.get("SimExpPan.lbl.longA1") +
+ trans.get("SimExpPan.lbl.longA2");
+ label.setToolTipText(tip);
+ panel.add(label, "gapright unrel");
+
+ fieldSeparator = new JComboBox(new String[] { ",", ";", SPACE, TAB });
+ fieldSeparator.setEditable(true);
+ fieldSeparator.setSelectedItem(Prefs.getString(Prefs.EXPORT_FIELD_SEPARATOR, ","));
+ fieldSeparator.setToolTipText(tip);
+ panel.add(fieldSeparator, "growx");
+
+ this.add(panel, "growx, wrap unrel");
+
+
+
+ // Comments separator panel
+ panel = new JPanel(new MigLayout("fill"));
+ panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Comments")));
+
+
+ // List of include comments options
+ if (includeComments.length % 2 == 1) {
+ throw new IllegalArgumentException("Invalid argument length, must be even, length=" + includeComments.length);
+ }
+ options = new JCheckBox[includeComments.length / 2];
+ for (int i = 0; i < includeComments.length / 2; i++) {
+ options[i] = new JCheckBox(includeComments[i * 2]);
+ options[i].setToolTipText(includeComments[i * 2 + 1]);
+ options[i].setSelected(Prefs.getBoolean("csvOptions." + baseClassName + "." + i, true));
+ panel.add(options[i], "wrap");
+ }
+
+
+ label = new JLabel(trans.get("SimExpPan.lbl.Commentchar"));
+ tip = trans.get("SimExpPan.lbl.ttip.Commentchar");
+ label.setToolTipText(tip);
+ panel.add(label, "split 2, gapright unrel");
+
+ commentCharacter = new JComboBox(new String[] { "#", "%", ";" });
+ commentCharacter.setEditable(true);
+ commentCharacter.setSelectedItem(Prefs.getString(Prefs.EXPORT_COMMENT_CHARACTER, "#"));
+ commentCharacter.setToolTipText(tip);
+ panel.add(commentCharacter, "growx");
+
+ this.add(panel, "growx, wrap");
+ }
+
+
+ public String getFieldSeparator() {
+ return fieldSeparator.getSelectedItem().toString();
+ }
+
+ public String getCommentCharacter() {
+ return commentCharacter.getSelectedItem().toString();
+ }
+
+ public boolean getSelectionOption(int index) {
+ return options[index].isSelected();
+ }
+
+ /**
+ * Store the selected options to the user preferences.
+ */
+ public void storePreferences() {
+ Prefs.putString(Prefs.EXPORT_FIELD_SEPARATOR, getFieldSeparator());
+ Prefs.putString(Prefs.EXPORT_COMMENT_CHARACTER, getCommentCharacter());
+ for (int i = 0; i < options.length; i++) {
+ Prefs.putBoolean("csvOptions." + baseClassName + "." + i, options[i].isSelected());
+ }
+ }
+
+}
private final JEditorPane editorPane;
+ /**
+ * Construct a description area with the specified number of rows, default description font size,
+ * being opaque.
+ *
+ * @param rows the number of rows
+ */
public DescriptionArea(int rows) {
this("", rows, -1);
}
+ /**
+ * Construct a description area with the specified number of rows and size, being opaque.
+ *
+ * @param rows the number of rows.
+ * @param size the font size difference compared to the default font size.
+ */
public DescriptionArea(int rows, float size) {
this("", rows, size);
}
+ /**
+ * Construct an opaque description area with the specified number of rows, size and text, being opaque.
+ *
+ * @param text the initial text.
+ * @param rows the number of rows.
+ * @param size the font size difference compared to the default font size.
+ */
public DescriptionArea(String text, int rows, float size) {
this(text, rows, size, true);
}
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import java.awt.Component;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+
+public class DoubleCellEditor extends AbstractCellEditor implements TableCellEditor {
+
+ private final JSpinner editor;
+ private final DoubleModel model;
+
+ public DoubleCellEditor() {
+ model = new DoubleModel(0);
+ editor = new JSpinner(model.getSpinnerModel());
+ editor.setEditor(new SpinnerEditor(editor));
+ // editor.addChangeListener(this);
+ }
+
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected, int row, int column) {
+
+ double val = (Double) value;
+ model.setValue(val);
+
+ return editor;
+ }
+
+
+ @Override
+ public Object getCellEditorValue() {
+ return model.getValue();
+ }
+
+}
import javax.swing.BorderFactory;
import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
-import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
-import javax.swing.filechooser.FileFilter;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.FileHelper;
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.Prefs;
import net.sf.openrocket.util.SaveCSVWorker;
public class SimulationExportPanel extends JPanel {
-
+
private static final String SPACE = "SPACE";
private static final String TAB = "TAB";
private static final Translator trans = Application.getTranslator();
- private static final FileFilter CSV_FILE_FILTER = new FileFilter() {
- @Override
- public String getDescription() {
- //// Comma Separated Files (*.csv)
- return trans.get("SimExpPan.desc");
- }
- @Override
- public boolean accept(File f) {
- if (f.isDirectory())
- return true;
- String name = f.getName().toLowerCase();
- return name.endsWith(".csv");
- }
- };
-
-
+ private static final int OPTION_SIMULATION_COMMENTS = 0;
+ private static final int OPTION_FIELD_DESCRIPTIONS = 1;
+ private static final int OPTION_FLIGHT_EVENTS = 2;
private final JTable table;
private final SelectionTableModel tableModel;
private final FlightDataType[] types;
private final Unit[] units;
- private final JComboBox fieldSeparator;
- private final JCheckBox simulationComments;
- private final JCheckBox fieldNameComments;
- private final JCheckBox eventComments;
- private final JComboBox commentCharacter;
+ private final CsvOptionPanel csvOptions;
public SimulationExportPanel(Simulation sim) {
super(new MigLayout("fill, flowy"));
-
- JLabel label;
+
JPanel panel;
JButton button;
- String tip;
-
+
this.simulation = sim;
// TODO: MEDIUM: Only exports primary branch
final FlightData data = simulation.getSimulatedData();
-
+
// Check that data exists
- if (data == null || data.getBranchCount() == 0 ||
+ if (data == null || data.getBranchCount() == 0 ||
data.getBranch(0).getTypes().length == 0) {
throw new IllegalArgumentException("No data for panel");
}
-
+
// Create the data model
branch = data.getBranch(0);
-
+
types = branch.getTypes();
Arrays.sort(types);
units[i] = types[i].getUnitGroup().getDefaultUnit();
}
-
+
//// Create the panel
-
+
// Set up the variable selection table
tableModel = new SelectionTableModel();
table = new JTable(tableModel);
- table.setDefaultRenderer(Object.class,
+ table.setDefaultRenderer(Object.class,
new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Object.class)));
- table.setDefaultRenderer(Boolean.class,
+ table.setDefaultRenderer(Boolean.class,
new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Boolean.class)));
table.setRowSelectionAllowed(false);
table.setColumnSelectionAllowed(false);
col = columnModel.getColumn(2);
col.setPreferredWidth(100);
-
+
table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
// Add table
});
panel.add(button, "growx 1, sizegroup selectbutton, wrap");
-
+
selectedCountLabel = new JLabel();
updateSelectedCount();
panel.add(selectedCountLabel);
this.add(panel, "grow 100, wrap");
+
+ // These need to be in the order of the OPTIONS_XXX indices
+ csvOptions = new CsvOptionPanel(SimulationExportPanel.class,
+ trans.get("SimExpPan.checkbox.Includesimudesc"),
+ trans.get("SimExpPan.checkbox.ttip.Includesimudesc"),
+ trans.get("SimExpPan.checkbox.Includefielddesc"),
+ trans.get("SimExpPan.checkbox.ttip.Includefielddesc"),
+ trans.get("SimExpPan.checkbox.Incflightevents"),
+ trans.get("SimExpPan.checkbox.ttip.Incflightevents"));
+ this.add(csvOptions, "spany, split, growx 1");
- // Field separator panel
- panel = new JPanel(new MigLayout("fill"));
- panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Fieldsep")));
-
- label = new JLabel(trans.get("SimExpPan.lbl.Fieldsepstr"));
- //// <html>The string used to separate the fields in the exported file.<br>
- //// Use ',' for a Comma Separated Values (CSV) file.
- tip = trans.get("SimExpPan.lbl.longA1") +
- trans.get("SimExpPan.lbl.longA2");
- label.setToolTipText(tip);
- panel.add(label);
-
- fieldSeparator = new JComboBox(new String[] { ",", ";", SPACE, TAB });
- fieldSeparator.setEditable(true);
- fieldSeparator.setSelectedItem(Prefs.getString(Prefs.EXPORT_FIELD_SEPARATOR,
- ","));
- fieldSeparator.setToolTipText(tip);
- panel.add(fieldSeparator);
-
- this.add(panel, "spany, split, growx 1");
-
-
-
-
- // Comments separator panel
- panel = new JPanel(new MigLayout("fill"));
- //// Comments
- panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Comments")));
-
- //// Include simulation description
- simulationComments = new JCheckBox(trans.get("SimExpPan.checkbox.Includesimudesc"));
- //// Include a comment at the beginning of the file describing the simulation.
- simulationComments.setToolTipText(trans.get("SimExpPan.checkbox.ttip.Includesimudesc"));
- simulationComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_SIMULATION_COMMENT,
- true));
- panel.add(simulationComments, "wrap");
-
- //// Include field descriptions
- fieldNameComments = new JCheckBox(trans.get("SimExpPan.checkbox.Includefielddesc"));
- //// Include a comment line with the descriptions of the exported variables.
- fieldNameComments.setToolTipText(trans.get("SimExpPan.checkbox.ttip.Includefielddesc"));
- fieldNameComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_FIELD_NAME_COMMENT, true));
- panel.add(fieldNameComments, "wrap");
-
-
- eventComments = new JCheckBox(trans.get("SimExpPan.checkbox.Incflightevents"));
- eventComments.setToolTipText(trans.get("SimExpPan.checkbox.ttip.Incflightevents"));
- eventComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_EVENT_COMMENTS, true));
- panel.add(eventComments, "wrap");
-
-
- label = new JLabel(trans.get("SimExpPan.lbl.Commentchar"));
- tip = trans.get("SimExpPan.lbl.ttip.Commentchar");
- label.setToolTipText(tip);
- panel.add(label, "split 2");
-
- commentCharacter = new JComboBox(new String[] { "#", "%", ";" });
- commentCharacter.setEditable(true);
- commentCharacter.setSelectedItem(Prefs.getString(Prefs.EXPORT_COMMENT_CHARACTER, "#"));
- commentCharacter.setToolTipText(tip);
- panel.add(commentCharacter);
-
- this.add(panel, "growx 1");
-
// Space-filling panel
panel = new JPanel();
this.add(panel, "width 1, height 1, grow 1");
-
+
// Export button
button = new JButton(trans.get("SimExpPan.but.Exporttofile"));
button.addActionListener(new ActionListener() {
private void doExport() {
JFileChooser chooser = new JFileChooser();
- chooser.setFileFilter(CSV_FILE_FILTER);
+ chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
if (file == null)
return;
- if (file.getName().indexOf('.') < 0) {
- String name = file.getAbsolutePath();
- name = name + ".csv";
- file = new File(name);
- }
-
- if (file.exists()) {
- int ret = JOptionPane.showConfirmDialog(this,
- //// File
- trans.get("SimExpPan.Fileexists.desc1") + file.getName() +
- //// \" exists. Overwrite?
- trans.get("SimExpPan.Fileexists.desc2"),
- //// File exists
- trans.get("SimExpPan.Fileexists.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
- if (ret != JOptionPane.YES_OPTION)
- return;
+ file = FileHelper.ensureExtension(file, "csv");
+ if (!FileHelper.confirmWrite(file, this)) {
+ return;
}
+
- String commentChar = commentCharacter.getSelectedItem().toString();
- String fieldSep = fieldSeparator.getSelectedItem().toString();
- boolean simulationComment = simulationComments.isSelected();
- boolean fieldComment = fieldNameComments.isSelected();
- boolean eventComment = eventComments.isSelected();
+ String commentChar = csvOptions.getCommentCharacter();
+ String fieldSep = csvOptions.getFieldSeparator();
+ boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS);
+ boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS);
+ boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS);
+ csvOptions.storePreferences();
// Store preferences and export
int n = 0;
Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
- for (int i=0; i < selected.length; i++) {
+ for (int i = 0; i < selected.length; i++) {
Prefs.setExportSelected(types[i], selected[i]);
if (selected[i])
n++;
}
- Prefs.putString(Prefs.EXPORT_FIELD_SEPARATOR, fieldSep);
- Prefs.putString(Prefs.EXPORT_COMMENT_CHARACTER, commentChar);
- Prefs.putBoolean(Prefs.EXPORT_EVENT_COMMENTS, eventComment);
- Prefs.putBoolean(Prefs.EXPORT_FIELD_NAME_COMMENT, fieldComment);
- Prefs.putBoolean(Prefs.EXPORT_SIMULATION_COMMENT, simulationComment);
-
+
FlightDataType[] fieldTypes = new FlightDataType[n];
Unit[] fieldUnits = new Unit[n];
int pos = 0;
- for (int i=0; i < selected.length; i++) {
+ for (int i = 0; i < selected.length; i++) {
if (selected[i]) {
fieldTypes[pos] = types[i];
fieldUnits[pos] = units[i];
fieldSep = "\t";
}
-
- SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep,
- commentChar, simulationComment, fieldComment, eventComment,
+
+ SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep,
+ commentChar, simulationComment, fieldComment, eventComment,
SwingUtilities.getWindowAncestor(this));
}
int n = 0;
String str;
- for (int i=0; i < selected.length; i++) {
+ for (int i = 0; i < selected.length; i++) {
if (selected[i])
n++;
}
} else {
//// Exporting
//// variables out of
- str = trans.get("SimExpPan.ExportingVar.desc2") + " "+n+" " +
- trans.get("SimExpPan.ExportingVar.desc3") + " " + total + ".";
+ str = trans.get("SimExpPan.ExportingVar.desc2") + " " + n + " " +
+ trans.get("SimExpPan.ExportingVar.desc3") + " " + total + ".";
}
-
+
selectedCountLabel.setText(str);
}
-
+
/**
* A table cell renderer that uses another renderer and sets the background and
* foreground of the returned component based on the selection of the variable.
*/
private class SelectionBackgroundCellRenderer implements TableCellRenderer {
-
+
private final TableCellRenderer renderer;
public SelectionBackgroundCellRenderer(TableCellRenderer renderer) {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
- Component component = renderer.getTableCellRendererComponent(table,
+ Component component = renderer.getTableCellRendererComponent(table,
value, isSelected, hasFocus, row, column);
if (selected[row]) {
private static final int SELECTED = 0;
private static final int NAME = 1;
private static final int UNIT = 2;
-
+
@Override
public int getColumnCount() {
return 3;
}
-
+
@Override
public int getRowCount() {
return types.length;
throw new IndexOutOfBoundsException("column=" + column);
}
}
-
+
@Override
public Object getValueAt(int row, int column) {
-
+
switch (column) {
case SELECTED:
return selected[row];
return units[row];
default:
- throw new IndexOutOfBoundsException("column="+column);
+ throw new IndexOutOfBoundsException("column=" + column);
}
}
-
+
@Override
public void setValueAt(Object value, int row, int column) {
switch (column) {
case SELECTED:
- selected[row] = (Boolean)value;
+ selected[row] = (Boolean) value;
this.fireTableRowsUpdated(row, row);
updateSelectedCount();
break;
-
+
case NAME:
break;
-
+
case UNIT:
- units[row] = (Unit)value;
+ units[row] = (Unit) value;
break;
-
+
default:
- throw new IndexOutOfBoundsException("column="+column);
+ throw new IndexOutOfBoundsException("column=" + column);
}
}
-
+
@Override
public boolean isCellEditable(int row, int column) {
switch (column) {
return types[row].getUnitGroup().getUnitCount() > 1;
default:
- throw new IndexOutOfBoundsException("column="+column);
+ throw new IndexOutOfBoundsException("column=" + column);
}
}
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public abstract class UnitCellEditor extends AbstractCellEditor
-implements TableCellEditor, ActionListener {
-
- private final JComboBox editor;
+public abstract class UnitCellEditor extends AbstractCellEditor
+ implements TableCellEditor, ActionListener {
+ private final JComboBox editor;
public UnitCellEditor() {
editor = new JComboBox();
@Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
-
+
Unit unit = (Unit) value;
UnitGroup group = getUnitGroup(unit, row, column);
editor.removeAllItems();
- for (Unit u: group.getUnits()) {
+ for (Unit u : group.getUnits()) {
editor.addItem(u);
}
return editor;
}
-
+
@Override
public Object getCellEditorValue() {
return editor.getSelectedItem();
}
-
+
@Override
public void actionPerformed(ActionEvent e) {
// End editing when a value has been selected
this.fireEditingStopped();
}
-
-
+
+
/**
* Return the unit group corresponding to the specified cell.
*
package net.sf.openrocket.gui.dialogs;
+import java.awt.SplashScreen;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.GUIUtil;
+/**
+ * A progress dialog displayed while loading motors.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class MotorDatabaseLoadingDialog extends JDialog {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
private MotorDatabaseLoadingDialog(Window parent) {
//// Loading motors
/**
* Check whether the motor database is loaded and block until it is.
- * An uncloseable modal dialog window is opened while loading.
+ * An uncloseable modal dialog window is opened while loading unless the splash screen
+ * is still being displayed.
*
* @param parent the parent window for the dialog, or <code>null</code>
*/
if (db.isLoaded())
return;
- log.info(1, "Motor database not loaded yet, displaying dialog");
-
- final MotorDatabaseLoadingDialog dialog = new MotorDatabaseLoadingDialog(parent);
-
- final Timer timer = new Timer(100, new ActionListener() {
- private int count = 0;
+ if (SplashScreen.getSplashScreen() == null) {
+
+ log.info(1, "Motor database not loaded yet, displaying dialog");
+
+ final MotorDatabaseLoadingDialog dialog = new MotorDatabaseLoadingDialog(parent);
+
+ final Timer timer = new Timer(100, new ActionListener() {
+ private int count = 0;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ count++;
+ if (db.isLoaded()) {
+ log.debug("Database loaded, closing dialog");
+ dialog.setVisible(false);
+ } else if (count % 10 == 0) {
+ log.debug("Database not loaded, count=" + count);
+ }
+ }
+ });
+
+ db.setInUse();
+ timer.start();
+ dialog.setVisible(true);
+ timer.stop();
+
+ } else {
+
+ log.info(1, "Motor database not loaded yet, splash screen still present, delaying until loaded");
- @Override
- public void actionPerformed(ActionEvent e) {
+ db.setInUse();
+ int count = 0;
+ while (!db.isLoaded()) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // No-op
+ }
+
count++;
- if (db.isLoaded()) {
- log.debug("Database loaded, closing dialog");
- dialog.setVisible(false);
- } else if (count % 10 == 0) {
+ if (count % 10 == 0) {
log.debug("Database not loaded, count=" + count);
}
}
- });
-
- db.setInUse();
- timer.start();
- dialog.setVisible(true);
- timer.stop();
+
+ }
- log.debug("Motor database now loaded");
+ log.info("Motor database now loaded");
}
}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.unit.Value;
+
+/**
+ * Value object for function evaluation information.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class FunctionEvaluationData {
+
+ private final Point point;
+ private final Value[] state;
+ private final Value domainReference;
+ private final Value parameterValue;
+ private final double goalValue;
+
+
+ public FunctionEvaluationData(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
+ this.point = point;
+ this.state = state.clone();
+ this.domainReference = domainReference;
+ this.parameterValue = parameterValue;
+ this.goalValue = goalValue;
+ }
+
+
+ /**
+ * Return the function evaluation point (in 0...1 range).
+ */
+ public Point getPoint() {
+ return point;
+ }
+
+
+ /**
+ * Return the function evaluation state in SI units + units.
+ */
+ public Value[] getState() {
+ return state;
+ }
+
+
+ /**
+ * Return the domain description.
+ */
+ public Value getDomainReference() {
+ return domainReference;
+ }
+
+
+ /**
+ * Return the optimization parameter value (or NaN is outside of domain).
+ */
+ public Value getParameterValue() {
+ return parameterValue;
+ }
+
+
+ /**
+ * Return the function goal value.
+ */
+ public double getGoalValue() {
+ return goalValue;
+ }
+}
package net.sf.openrocket.gui.dialogs.optimization;
+import java.awt.Component;
import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
-import javax.swing.JTree;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.JToggleButton;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableColumnModel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.components.CsvOptionPanel;
+import net.sf.openrocket.gui.components.DescriptionArea;
+import net.sf.openrocket.gui.components.DoubleCellEditor;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.components.UnitCellEditor;
+import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.scalefigure.RocketFigure;
+import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.optimization.rocketoptimization.domains.IdentitySimulationDomain;
+import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
+import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.goals.MinimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.goals.ValueSeekGoal;
import net.sf.openrocket.optimization.services.OptimizationServiceHelper;
import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.CaliberUnit;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Chars;
+import net.sf.openrocket.util.FileHelper;
import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Named;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.TextUtil;
+import com.itextpdf.text.Font;
+
+/**
+ * General rocket optimization dialog.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class GeneralOptimizationDialog extends JDialog {
+ private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
+ private static final Collator collator = Collator.getInstance();
+
+
+ private static final String GOAL_MAXIMIZE = trans.get("goal.maximize");
+ private static final String GOAL_MINIMIZE = trans.get("goal.minimize");
+ private static final String GOAL_SEEK = trans.get("goal.seek");
+
+ private static final String START_TEXT = trans.get("btn.start");
+ private static final String STOP_TEXT = trans.get("btn.stop");
+
+
+
private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
private final Map<Object, List<SimulationModifier>> simulationModifiers =
new HashMap<Object, List<SimulationModifier>>();
private final OpenRocketDocument baseDocument;
- private final Rocket rocketCopy;
+ private OpenRocketDocument documentCopy;
+
+
+ private final JButton addButton;
+ private final JButton removeButton;
+ private final JButton removeAllButton;
+
+ private final ParameterSelectionTableModel selectedModifierTableModel;
+ private final JTable selectedModifierTable;
+ private final DescriptionArea selectedModifierDescription;
+ private final SimulationModifierTree availableModifierTree;
+
+ private final JComboBox simulationSelectionCombo;
+ private final JComboBox optimizationParameterCombo;
+
+ private final JComboBox optimizationGoalCombo;
+ private final JSpinner optimizationGoalSpinner;
+ private final UnitSelector optimizationGoalUnitSelector;
+ private final DoubleModel optimizationSeekValue;
+
+ private DoubleModel minimumStability;
+ private DoubleModel maximumStability;
+ private final JCheckBox minimumStabilitySelected;
+ private final JSpinner minimumStabilitySpinner;
+ private final UnitSelector minimumStabilityUnitSelector;
+ private final JCheckBox maximumStabilitySelected;
+ private final JSpinner maximumStabilitySpinner;
+ private final UnitSelector maximumStabilityUnitSelector;
+ private final JLabel bestValueLabel;
+ private final JLabel stepCountLabel;
+ private final JLabel evaluationCountLabel;
+ private final JLabel stepSizeLabel;
+ private final RocketFigure figure;
+ private final JToggleButton startButton;
+ private final JButton plotButton;
+ private final JButton saveButton;
+
+ private final JButton applyButton;
+ private final JButton resetButton;
+ private final JButton closeButton;
+
+ private final List<SimulationModifier> selectedModifiers = new ArrayList<SimulationModifier>();
+
+ /** List of components to disable while optimization is running */
+ private final List<JComponent> disableComponents = new ArrayList<JComponent>();
+
+ /** Whether optimization is currently running or not */
+ private boolean running = false;
+ /** The optimization worker that is running */
+ private OptimizationWorker worker = null;
+
+
+ private double bestValue = Double.NaN;
+ private Unit bestValueUnit = Unit.NOUNIT2;
+ private int stepCount = 0;
+ private int evaluationCount = 0;
+ private double stepSize = 0;
+
+ private final Map<Point, FunctionEvaluationData> evaluationHistory = new LinkedHashMap<Point, FunctionEvaluationData>();
+ private final List<Point> optimizationPath = new LinkedList<Point>();
+
+
+ private boolean updating = false;
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param document the document
+ * @param parent the parent window
+ */
public GeneralOptimizationDialog(OpenRocketDocument document, Window parent) {
- super(parent, "Rocket optimization");
+ super(parent, trans.get("title"));
this.baseDocument = document;
- this.rocketCopy = document.getRocket().copyWithOriginalID();
+ this.documentCopy = document.copy();
loadOptimizationParameters();
loadSimulationModifiers();
-
+ JPanel sub;
+ JLabel label;
+ JScrollPane scroll;
+ String tip;
+
JPanel panel = new JPanel(new MigLayout("fill"));
- JTree tree = new SimulationModifierTree(rocketCopy, simulationModifiers);
- JScrollPane scroll = new JScrollPane(tree);
- panel.add(scroll, "width 300lp, height 300lp");
+ ChangeListener clearHistoryChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ clearHistory();
+ }
+ };
+ ActionListener clearHistoryActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ clearHistory();
+ }
+ };
+
+
+
+ //// Selected modifiers table
+
+ selectedModifierTableModel = new ParameterSelectionTableModel();
+ selectedModifierTable = new JTable(selectedModifierTableModel);
+ selectedModifierTable.setDefaultRenderer(Double.class, new DoubleCellRenderer());
+ selectedModifierTable.setRowSelectionAllowed(true);
+ selectedModifierTable.setColumnSelectionAllowed(false);
+ selectedModifierTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ // Make sure spinner editor fits into the cell height
+ selectedModifierTable.setRowHeight(new JSpinner().getPreferredSize().height - 4);
+
+ selectedModifierTable.setDefaultEditor(Double.class, new DoubleCellEditor());
+ selectedModifierTable.setDefaultEditor(Unit.class, new UnitCellEditor() {
+ @Override
+ protected UnitGroup getUnitGroup(Unit value, int row, int column) {
+ return selectedModifiers.get(row).getUnitGroup();
+ }
+ });
+
+ disableComponents.add(selectedModifierTable);
+
+ selectedModifierTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ updateComponents();
+ }
+ });
+
+ // Set column widths
+ TableColumnModel columnModel = selectedModifierTable.getColumnModel();
+ columnModel.getColumn(0).setPreferredWidth(150);
+ columnModel.getColumn(1).setPreferredWidth(40);
+ columnModel.getColumn(2).setPreferredWidth(40);
+ columnModel.getColumn(3).setPreferredWidth(40);
+
+ scroll = new JScrollPane(selectedModifierTable);
+
+ label = new StyledLabel(trans.get("lbl.paramsToOptimize"), Style.BOLD);
+ disableComponents.add(label);
+ panel.add(label, "split 3, flowy");
+ panel.add(scroll, "wmin 300lp, height 200lp, grow");
+ selectedModifierDescription = new DescriptionArea(2, -3);
+ disableComponents.add(selectedModifierDescription);
+ panel.add(selectedModifierDescription, "growx");
+
+
+
+ //// Add/remove buttons
+ sub = new JPanel(new MigLayout("fill"));
+
+ addButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.add") + " ");
+ addButton.setToolTipText(trans.get("btn.add.ttip"));
+ addButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SimulationModifier mod = getSelectedAvailableModifier();
+ if (mod != null) {
+ addModifier(mod);
+ clearHistory();
+ } else {
+ log.error("Attempting to add simulation modifier when none is selected");
+ }
+ }
+ });
+ disableComponents.add(addButton);
+ sub.add(addButton, "wrap para, sg button");
+
+ removeButton = new JButton(" " + trans.get("btn.remove") + " " + Chars.RIGHT_ARROW);
+ removeButton.setToolTipText(trans.get("btn.remove.ttip"));
+ removeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SimulationModifier mod = getSelectedModifier();
+ if (mod == null) {
+ log.error("Attempting to remove simulation modifier when none is selected");
+ return;
+ }
+ removeModifier(mod);
+ clearHistory();
+ }
+ });
+ disableComponents.add(removeButton);
+ sub.add(removeButton, "wrap para*2, sg button");
+
+ removeAllButton = new JButton(trans.get("btn.removeAll"));
+ removeAllButton.setToolTipText(trans.get("btn.removeAll.ttip"));
+ removeAllButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Removing all selected modifiers");
+ selectedModifiers.clear();
+ selectedModifierTableModel.fireTableDataChanged();
+ availableModifierTree.repaint();
+ clearHistory();
+ }
+ });
+ disableComponents.add(removeAllButton);
+ sub.add(removeAllButton, "wrap para, sg button");
+
+ panel.add(sub);
+
+
+
+ //// Available modifier tree
+ availableModifierTree = new SimulationModifierTree(documentCopy.getRocket(), simulationModifiers, selectedModifiers);
+ availableModifierTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ updateComponents();
+ }
+ });
+
+ // Handle double-click
+ availableModifierTree.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ SimulationModifier mod = getSelectedAvailableModifier();
+ if (mod != null) {
+ addModifier(mod);
+ clearHistory();
+ } else {
+ log.user("Double-clicked non-available option");
+ }
+ }
+ }
+ });
+
+ disableComponents.add(availableModifierTree);
+ scroll = new JScrollPane(availableModifierTree);
+ label = new StyledLabel(trans.get("lbl.availableParams"), Style.BOLD);
+ disableComponents.add(label);
+ panel.add(label, "split 2, flowy");
+ panel.add(scroll, "width 300lp, height 200lp, grow, wrap para*2");
+
+
+
+
+ //// Optimization options sub-panel
+
+ sub = new JPanel(new MigLayout("fill"));
+ TitledBorder border = BorderFactory.createTitledBorder(trans.get("lbl.optimizationOpts"));
+ GUIUtil.changeFontStyle(border, Font.BOLD);
+ sub.setBorder(border);
+ disableComponents.add(sub);
+
+
+ //// Simulation to optimize
+
+ label = new JLabel(trans.get("lbl.optimizeSim"));
+ tip = trans.get("lbl.optimizeSim.ttip");
+ label.setToolTipText(tip);
+ disableComponents.add(label);
+ sub.add(label, "");
+
+ simulationSelectionCombo = new JComboBox();
+ simulationSelectionCombo.setToolTipText(tip);
+ populateSimulations();
+ simulationSelectionCombo.addActionListener(clearHistoryActionListener);
+ disableComponents.add(simulationSelectionCombo);
+ sub.add(simulationSelectionCombo, "growx, wrap unrel");
+
+
+
+ //// Value to optimize
+ label = new JLabel(trans.get("lbl.optimizeValue"));
+ tip = trans.get("lbl.optimizeValue.ttip");
+ label.setToolTipText(tip);
+ disableComponents.add(label);
+ sub.add(label, "");
+
+ optimizationParameterCombo = new JComboBox();
+ optimizationParameterCombo.setToolTipText(tip);
+ populateParameters();
+ optimizationParameterCombo.addActionListener(clearHistoryActionListener);
+ disableComponents.add(optimizationParameterCombo);
+ sub.add(optimizationParameterCombo, "growx, wrap unrel");
+
+
+
+ //// Optimization goal
+ label = new JLabel(trans.get("lbl.optimizeGoal"));
+ tip = trans.get("lbl.optimizeGoal");
+ label.setToolTipText(tip);
+ disableComponents.add(label);
+ sub.add(label, "");
+
+ optimizationGoalCombo = new JComboBox(new String[] { GOAL_MAXIMIZE, GOAL_MINIMIZE, GOAL_SEEK });
+ optimizationGoalCombo.setToolTipText(tip);
+ optimizationGoalCombo.setEditable(false);
+ optimizationGoalCombo.addActionListener(clearHistoryActionListener);
+ disableComponents.add(optimizationGoalCombo);
+ sub.add(optimizationGoalCombo, "growx");
+
+
+ //// Optimization custom value
+ optimizationSeekValue = new DoubleModel(0, UnitGroup.UNITS_NONE);
+ optimizationSeekValue.addChangeListener(clearHistoryChangeListener);
+
+ optimizationGoalSpinner = new JSpinner(optimizationSeekValue.getSpinnerModel());
+ tip = trans.get("lbl.optimizeGoalValue.ttip");
+ optimizationGoalSpinner.setToolTipText(tip);
+ optimizationGoalSpinner.setEditor(new SpinnerEditor(optimizationGoalSpinner));
+ disableComponents.add(optimizationGoalSpinner);
+ sub.add(optimizationGoalSpinner, "width 30lp");
+
+ optimizationGoalUnitSelector = new UnitSelector(optimizationSeekValue);
+ optimizationGoalUnitSelector.setToolTipText(tip);
+ disableComponents.add(optimizationGoalUnitSelector);
+ sub.add(optimizationGoalUnitSelector, "width 20lp, wrap unrel");
+
+
+ panel.add(sub, "grow");
+
+
+
+ //// Required stability sub-panel
+
+ sub = new JPanel(new MigLayout("fill"));
+ border = BorderFactory.createTitledBorder(trans.get("lbl.requireStability"));
+ GUIUtil.changeFontStyle(border, Font.BOLD);
+ sub.setBorder(border);
+ disableComponents.add(sub);
+
+
+
+ double ref = CaliberUnit.calculateCaliber(baseDocument.getRocket());
+ minimumStability = new DoubleModel(ref, UnitGroup.stabilityUnits(ref));
+ maximumStability = new DoubleModel(5 * ref, UnitGroup.stabilityUnits(ref));
+ minimumStability.addChangeListener(clearHistoryChangeListener);
+ maximumStability.addChangeListener(clearHistoryChangeListener);
+
+
+ //// Minimum stability
+ tip = trans.get("lbl.requireMinStability.ttip");
+ minimumStabilitySelected = new JCheckBox(trans.get("lbl.requireMinStability"));
+ minimumStabilitySelected.setSelected(true);
+ minimumStabilitySelected.setToolTipText(tip);
+ minimumStabilitySelected.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateComponents();
+ }
+ });
+ disableComponents.add(minimumStabilitySelected);
+ sub.add(minimumStabilitySelected);
+
+ minimumStabilitySpinner = new JSpinner(minimumStability.getSpinnerModel());
+ minimumStabilitySpinner.setToolTipText(tip);
+ minimumStabilitySpinner.setEditor(new SpinnerEditor(minimumStabilitySpinner));
+ disableComponents.add(minimumStabilitySpinner);
+ sub.add(minimumStabilitySpinner, "growx");
+
+ minimumStabilityUnitSelector = new UnitSelector(minimumStability);
+ minimumStabilityUnitSelector.setToolTipText(tip);
+ disableComponents.add(minimumStabilityUnitSelector);
+ sub.add(minimumStabilityUnitSelector, "growx, wrap unrel");
+
+
+ //// Maximum stability
+ tip = trans.get("lbl.requireMaxStability.ttip");
+ maximumStabilitySelected = new JCheckBox(trans.get("lbl.requireMaxStability"));
+ maximumStabilitySelected.setToolTipText(tip);
+ maximumStabilitySelected.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateComponents();
+ }
+ });
+ disableComponents.add(maximumStabilitySelected);
+ sub.add(maximumStabilitySelected);
+
+ maximumStabilitySpinner = new JSpinner(maximumStability.getSpinnerModel());
+ maximumStabilitySpinner.setToolTipText(tip);
+ maximumStabilitySpinner.setEditor(new SpinnerEditor(maximumStabilitySpinner));
+ disableComponents.add(maximumStabilitySpinner);
+ sub.add(maximumStabilitySpinner, "growx");
+
+ maximumStabilityUnitSelector = new UnitSelector(maximumStability);
+ maximumStabilityUnitSelector.setToolTipText(tip);
+ disableComponents.add(maximumStabilityUnitSelector);
+ sub.add(maximumStabilityUnitSelector, "growx, wrap para");
+
+
+
+ // DescriptionArea desc = new DescriptionArea("Stability requirements are verified during each time step of the simulation.",
+ // 2, -2, false);
+ // desc.setViewportBorder(null);
+ // disableComponents.add(desc);
+ // sub.add(desc, "span, growx");
+
+
+ panel.add(sub, "span 2, grow, wrap para*2");
+
+
+
+
+ //// Rocket figure
+ figure = new RocketFigure(getSelectedSimulation().getConfiguration());
+ figure.setBorderPixels(1, 1);
+ ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
+ figureScrollPane.setFitting(true);
+ panel.add(figureScrollPane, "span, split, height 200lp, grow");
+
+
+ sub = new JPanel(new MigLayout("fill"));
+
+
+ label = new JLabel(trans.get("status.bestValue"));
+ tip = trans.get("status.bestValue.ttip");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ bestValueLabel = new JLabel();
+ bestValueLabel.setToolTipText(tip);
+ sub.add(bestValueLabel, "wmin 60lp, wrap rel");
+
+
+ label = new JLabel(trans.get("status.stepCount"));
+ tip = trans.get("status.stepCount.ttip");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ stepCountLabel = new JLabel();
+ stepCountLabel.setToolTipText(tip);
+ sub.add(stepCountLabel, "wrap rel");
+
+
+ label = new JLabel(trans.get("status.evalCount"));
+ tip = trans.get("status.evalCount");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ evaluationCountLabel = new JLabel();
+ evaluationCountLabel.setToolTipText(tip);
+ sub.add(evaluationCountLabel, "wrap rel");
+
+
+ label = new JLabel(trans.get("status.stepSize"));
+ tip = trans.get("status.stepSize.ttip");
+ label.setToolTipText(tip);
+ sub.add(label, "gapright unrel");
+
+ stepSizeLabel = new JLabel();
+ stepSizeLabel.setToolTipText(tip);
+ sub.add(stepSizeLabel, "wrap para");
+
+
+ //// Start/Stop button
+
+ startButton = new JToggleButton(START_TEXT);
+ startButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (updating) {
+ log.debug("Updating, ignoring event");
+ return;
+ }
+ if (running) {
+ log.user("Stopping optimization");
+ stopOptimization();
+ } else {
+ log.user("Starting optimization");
+ startOptimization();
+ }
+ }
+ });
+ sub.add(startButton, "span, growx, wrap para*2");
+
+
+ plotButton = new JButton(trans.get("btn.plotPath"));
+ plotButton.setToolTipText(trans.get("btn.plotPath.ttip"));
+ plotButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Plotting optimization path, dimensionality=" + selectedModifiers.size());
+ OptimizationPlotDialog dialog = new OptimizationPlotDialog(optimizationPath, evaluationHistory,
+ selectedModifiers, getSelectedParameter(),
+ UnitGroup.stabilityUnits(getSelectedSimulation().getRocket()),
+ GeneralOptimizationDialog.this);
+ dialog.setVisible(true);
+ }
+ });
+ disableComponents.add(plotButton);
+ sub.add(plotButton, "span, growx, wrap");
+
+
+ saveButton = new JButton(trans.get("btn.save"));
+ saveButton.setToolTipText(trans.get("btn.save.ttip"));
+ saveButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("User selected save path");
+ savePath();
+ }
+ });
+ disableComponents.add(saveButton);
+ sub.add(saveButton, "span, growx");
+
+
+
+ panel.add(sub, "wrap para*2");
+
+
+
+
+ //// Bottom buttons
+
+ applyButton = new JButton(trans.get("btn.apply"));
+ applyButton.setToolTipText(trans.get("btn.apply.ttip"));
+ applyButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Applying optimization changes");
+ applyDesign();
+ }
+ });
+ disableComponents.add(applyButton);
+ panel.add(applyButton, "span, split, gapright para, right");
+
+ resetButton = new JButton(trans.get("btn.reset"));
+ resetButton.setToolTipText(trans.get("btn.reset.ttip"));
+ resetButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Resetting optimization design");
+ resetDesign();
+ }
+ });
+ disableComponents.add(resetButton);
+ panel.add(resetButton, "gapright para, right");
+
+ closeButton = new JButton(trans.get("btn.close"));
+ closeButton.setToolTipText(trans.get("btn.close.ttip"));
+ closeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Closing optimization dialog");
+ stopOptimization();
+ GeneralOptimizationDialog.this.dispose();
+ }
+ });
+ panel.add(closeButton, "right");
this.add(panel);
+ clearHistory();
+ updateComponents();
GUIUtil.setDisposableDialogOptions(this, null);
}
+ private void startOptimization() {
+ if (running) {
+ log.info("Optimization already running");
+ return;
+ }
+
+
+ if (selectedModifiers.isEmpty()) {
+ JOptionPane.showMessageDialog(this, trans.get("error.selectParams.text"),
+ trans.get("error.selectParams.title"), JOptionPane.ERROR_MESSAGE);
+ updating = true;
+ startButton.setSelected(false);
+ startButton.setText(START_TEXT);
+ updating = false;
+ return;
+ }
+
+
+ running = true;
+
+ // Update the button status
+ updating = true;
+ startButton.setSelected(true);
+ startButton.setText(STOP_TEXT);
+ updating = false;
+
+
+ // Create a copy of the simulation (we're going to modify the original in the current thread)
+ Simulation simulation = getSelectedSimulation();
+ Rocket rocketCopy = simulation.getRocket().copyWithOriginalID();
+ simulation = simulation.duplicateSimulation(rocketCopy);
+
+ OptimizableParameter parameter = getSelectedParameter();
+
+ OptimizationGoal goal;
+ String value = (String) optimizationGoalCombo.getSelectedItem();
+ if (GOAL_MAXIMIZE.equals(value)) {
+ goal = new MaximizationGoal();
+ } else if (GOAL_MINIMIZE.equals(value)) {
+ goal = new MinimizationGoal();
+ } else if (GOAL_SEEK.equals(value)) {
+ goal = new ValueSeekGoal(optimizationSeekValue.getValue());
+ } else {
+ throw new BugException("optimizationGoalCombo had invalid value: " + value);
+ }
+
+ SimulationDomain domain;
+ if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+ double min, max;
+ boolean minAbsolute, maxAbsolute;
+
+ /*
+ * Make minAbsolute/maxAbsolute consistent with each other to produce reasonable
+ * result in plot tool tips. Yes, this is a bit ugly.
+ */
+
+ // Min stability
+ Unit unit = minimumStability.getCurrentUnit();
+ if (unit instanceof CaliberUnit) {
+ min = unit.toUnit(minimumStability.getValue());
+ minAbsolute = false;
+ } else {
+ min = minimumStability.getValue();
+ minAbsolute = true;
+ }
+
+ // Max stability
+ unit = maximumStability.getCurrentUnit();
+ if (unit instanceof CaliberUnit) {
+ max = unit.toUnit(maximumStability.getValue());
+ maxAbsolute = false;
+ } else {
+ max = maximumStability.getValue();
+ maxAbsolute = true;
+ }
+
+
+ if (!minimumStabilitySelected.isSelected()) {
+ min = Double.NaN;
+ minAbsolute = maxAbsolute;
+ }
+ if (!maximumStabilitySelected.isSelected()) {
+ max = Double.NaN;
+ maxAbsolute = minAbsolute;
+ }
+
+ domain = new StabilityDomain(min, minAbsolute, max, maxAbsolute);
+ } else {
+ domain = new IdentitySimulationDomain();
+ }
+
+ SimulationModifier[] modifiers = selectedModifiers.toArray(new SimulationModifier[0]);
+
+ // Create and start the background worker
+ worker = new OptimizationWorker(simulation, parameter, goal, domain, modifiers) {
+ @Override
+ protected void done(OptimizationException exception) {
+ log.info("Optimization finished, exception=" + exception, exception);
+
+ if (exception != null) {
+ JOptionPane.showMessageDialog(GeneralOptimizationDialog.this,
+ new Object[] {
+ trans.get("error.optimizationFailure.text"),
+ exception.getLocalizedMessage()
+ }, trans.get("error.optimizationFailure.title"), JOptionPane.ERROR_MESSAGE);
+ }
+
+ worker = null;
+ stopOptimization();
+ }
+
+ @Override
+ protected void functionEvaluated(List<FunctionEvaluationData> data) {
+ for (FunctionEvaluationData d : data) {
+ evaluationHistory.put(d.getPoint(), d);
+ evaluationCount++;
+ }
+ updateCounters();
+ }
+
+ @Override
+ protected void optimizationStepTaken(List<OptimizationStepData> data) {
+
+ // Add starting point to the path
+ if (optimizationPath.isEmpty()) {
+ optimizationPath.add(data.get(0).getOldPoint());
+ }
+
+ // Add other points to the path
+ for (OptimizationStepData d : data) {
+ optimizationPath.add(d.getNewPoint());
+ }
+
+ // Get function value from the latest point
+ OptimizationStepData latest = data.get(data.size() - 1);
+ Point newPoint = latest.getNewPoint();
+
+ FunctionEvaluationData pointValue = evaluationHistory.get(newPoint);
+ if (pointValue != null) {
+ bestValue = pointValue.getParameterValue().getValue();
+ } else {
+ log.error("History does not contain point " + newPoint);
+ bestValue = Double.NaN;
+ }
+
+ // Update the simulation
+ Simulation sim = getSelectedSimulation();
+ for (int i = 0; i < newPoint.dim(); i++) {
+ try {
+ selectedModifiers.get(i).modify(sim, newPoint.get(i));
+ } catch (OptimizationException e) {
+ throw new BugException("Simulation modifier failed to modify the base simulation " +
+ "modifier=" + selectedModifiers.get(i), e);
+ }
+ }
+ figure.updateFigure();
+
+ // Update other counter data
+ stepCount += data.size();
+ stepSize = latest.getStepSize();
+ updateCounters();
+ }
+ };
+ worker.start();
+
+
+ clearHistory();
+
+ updateComponents();
+ }
+
+ private void stopOptimization() {
+ if (!running) {
+ log.info("Optimization not running");
+ return;
+ }
+
+ if (worker != null && worker.isAlive()) {
+ log.info("Worker still running, interrupting it and setting to null");
+ worker.interrupt();
+ worker = null;
+ return;
+ }
+
+ running = false;
+
+ // Update the button status
+ updating = true;
+ startButton.setSelected(false);
+ startButton.setText(START_TEXT);
+ updating = false;
+
+
+ updateComponents();
+ }
+
+
+
+
+ /**
+ * Reset the current optimization history and values. This does not reset the design.
+ */
+ private void clearHistory() {
+ evaluationHistory.clear();
+ optimizationPath.clear();
+ bestValue = Double.NaN;
+ bestValueUnit = getSelectedParameter().getUnitGroup().getDefaultUnit();
+ stepCount = 0;
+ evaluationCount = 0;
+ stepSize = 0.5;
+ updateCounters();
+ updateComponents();
+ }
+
+
+ private void applyDesign() {
+ // TODO: MEDIUM: Apply also potential changes to simulations
+ Rocket src = getSelectedSimulation().getRocket().copyWithOriginalID();
+ Rocket dest = baseDocument.getRocket();
+ try {
+ baseDocument.startUndo(trans.get("undoText"));
+ dest.freeze();
+
+ // Remove all children
+ while (dest.getChildCount() > 0) {
+ dest.removeChild(0);
+ }
+
+ // Move all children to the destination rocket
+ while (src.getChildCount() > 0) {
+ RocketComponent c = src.getChild(0);
+ src.removeChild(0);
+ dest.addChild(c);
+ }
+
+ } finally {
+ dest.thaw();
+ baseDocument.stopUndo();
+ }
+ }
+
+
+ private void resetDesign() {
+ clearHistory();
+
+ documentCopy = baseDocument.copy();
+
+ loadOptimizationParameters();
+ loadSimulationModifiers();
+
+ // Replace selected modifiers with corresponding new modifiers
+ List<SimulationModifier> newSelected = new ArrayList<SimulationModifier>();
+ for (SimulationModifier original : selectedModifiers) {
+ List<SimulationModifier> newModifiers = simulationModifiers.get(original.getRelatedObject());
+ if (newModifiers != null) {
+ int index = newModifiers.indexOf(original);
+ if (index >= 0) {
+ newSelected.add(newModifiers.get(index));
+ }
+ }
+ }
+ selectedModifiers.clear();
+ selectedModifiers.addAll(newSelected);
+ selectedModifierTableModel.fireTableDataChanged();
+
+ // Update the available modifier tree
+ availableModifierTree.populateTree(documentCopy.getRocket(), simulationModifiers);
+ availableModifierTree.expandComponents();
+
+
+ // Update selectable simulations
+ populateSimulations();
+
+ // Update selectable parameters
+ populateParameters();
+
+ }
+
+
+ private void populateSimulations() {
+ String current = null;
+ Object selection = simulationSelectionCombo.getSelectedItem();
+ if (selection != null) {
+ current = selection.toString();
+ }
+
+
+ List<Named<Simulation>> simulations = new ArrayList<Named<Simulation>>();
+ Rocket rocket = documentCopy.getRocket();
+
+ for (Simulation s : documentCopy.getSimulations()) {
+ String id = s.getConfiguration().getMotorConfigurationID();
+ String name = createSimulationName(s.getName(), rocket.getMotorConfigurationNameOrDescription(id));
+ simulations.add(new Named<Simulation>(s, name));
+ }
+
+ for (String id : rocket.getMotorConfigurationIDs()) {
+ if (id == null) {
+ continue;
+ }
+ Simulation sim = new Simulation(rocket);
+ sim.getConfiguration().setMotorConfigurationID(id);
+ String name = createSimulationName(trans.get("basicSimulationName"), rocket.getMotorConfigurationNameOrDescription(id));
+ simulations.add(new Named<Simulation>(sim, name));
+ }
+
+
+ Simulation sim = new Simulation(rocket);
+ sim.getConfiguration().setMotorConfigurationID(null);
+ String name = createSimulationName(trans.get("noSimulationName"), rocket.getMotorConfigurationNameOrDescription(null));
+ simulations.add(new Named<Simulation>(sim, name));
+
+
+ simulationSelectionCombo.setModel(new DefaultComboBoxModel(simulations.toArray()));
+
+ if (current != null) {
+ for (int i = 0; i < simulations.size(); i++) {
+ if (simulations.get(i).toString().equals(current)) {
+ simulationSelectionCombo.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+ }
+
+
+ private void populateParameters() {
+ String current = null;
+ Object selection = optimizationParameterCombo.getSelectedItem();
+ if (selection != null) {
+ current = selection.toString();
+ } else {
+ // Default to apogee altitude event if it is not the first one in the list
+ current = trans.get("MaximumAltitudeParameter.name");
+ }
+
+ List<Named<OptimizableParameter>> parameters = new ArrayList<Named<OptimizableParameter>>();
+ for (OptimizableParameter p : optimizationParameters) {
+ parameters.add(new Named<OptimizableParameter>(p, p.getName()));
+ }
+
+ optimizationParameterCombo.setModel(new DefaultComboBoxModel(parameters.toArray()));
+
+ for (int i = 0; i < parameters.size(); i++) {
+ if (parameters.get(i).toString().equals(current)) {
+ optimizationParameterCombo.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+
+ private void updateCounters() {
+ bestValueLabel.setText(bestValueUnit.toStringUnit(bestValue));
+ stepCountLabel.setText("" + stepCount);
+ evaluationCountLabel.setText("" + evaluationCount);
+ stepSizeLabel.setText(UnitGroup.UNITS_RELATIVE.toStringUnit(stepSize));
+ }
+
+
private void loadOptimizationParameters() {
- optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(baseDocument));
+ optimizationParameters.clear();
+ optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(documentCopy));
if (optimizationParameters.isEmpty()) {
throw new BugException("No rocket optimization parameters found, distribution built wrong.");
private void loadSimulationModifiers() {
+ simulationModifiers.clear();
- for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(baseDocument)) {
+ for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(documentCopy)) {
Object key = m.getRelatedObject();
List<SimulationModifier> list = simulationModifiers.get(key);
if (list == null) {
}
+
+
+ private void addModifier(SimulationModifier mod) {
+ if (!selectedModifiers.contains(mod)) {
+ log.user(1, "Adding simulation modifier " + mod);
+ selectedModifiers.add(mod);
+ Collections.sort(selectedModifiers, new SimulationModifierComparator());
+ selectedModifierTableModel.fireTableDataChanged();
+ availableModifierTree.repaint();
+ } else {
+ log.user(1, "Attempting to add an already existing simulation modifier " + mod);
+ }
+ }
+
+
+ private void removeModifier(SimulationModifier mod) {
+ log.user(1, "Removing simulation modifier " + mod);
+ selectedModifiers.remove(mod);
+ selectedModifierTableModel.fireTableDataChanged();
+ availableModifierTree.repaint();
+ }
+
+
+
+ /**
+ * Update the enabled status of all components in the dialog.
+ */
+ private void updateComponents() {
+
+ if (updating) {
+ return;
+ }
+
+ updating = true;
+
+
+ // First enable all components if optimization not running
+ if (!running) {
+ for (JComponent c : disableComponents) {
+ c.setEnabled(true);
+ }
+ }
+
+
+ // "Add" button
+ SimulationModifier mod = getSelectedAvailableModifier();
+ if (mod != null && !selectedModifiers.contains(mod)) {
+ addButton.setEnabled(true);
+ } else {
+ addButton.setEnabled(false);
+ }
+
+ // "Remove" button
+ removeButton.setEnabled(selectedModifierTable.getSelectedRow() >= 0);
+
+ // "Remove all" button
+ removeAllButton.setEnabled(!selectedModifiers.isEmpty());
+
+
+ // Optimization goal
+ String selected = (String) optimizationGoalCombo.getSelectedItem();
+ if (GOAL_SEEK.equals(selected)) {
+ optimizationGoalSpinner.setVisible(true);
+ optimizationGoalUnitSelector.setVisible(true);
+ } else {
+ optimizationGoalSpinner.setVisible(false);
+ optimizationGoalUnitSelector.setVisible(false);
+ }
+
+
+ // Minimum/maximum stability options
+ minimumStabilitySpinner.setEnabled(minimumStabilitySelected.isSelected());
+ minimumStabilityUnitSelector.setEnabled(minimumStabilitySelected.isSelected());
+ maximumStabilitySpinner.setEnabled(maximumStabilitySelected.isSelected());
+ maximumStabilityUnitSelector.setEnabled(maximumStabilitySelected.isSelected());
+
+
+ // Plot button (enabled if path exists and dimensionality is 1 or 2)
+ plotButton.setEnabled(!optimizationPath.isEmpty() && (selectedModifiers.size() == 1 || selectedModifiers.size() == 2));
+
+ // Save button (enabled if path exists)
+ saveButton.setEnabled(!optimizationPath.isEmpty());
+
+
+ // Last disable all components if optimization is running
+ if (running) {
+ for (JComponent c : disableComponents) {
+ c.setEnabled(false);
+ }
+ }
+
+
+ // Update description text
+ mod = getSelectedModifier();
+ if (mod != null) {
+ selectedModifierDescription.setText(mod.getDescription());
+ } else {
+ selectedModifierDescription.setText("");
+ }
+
+
+ // Update the figure
+ figure.setConfiguration(getSelectedSimulation().getConfiguration());
+
+ updating = false;
+ }
+
+
+ private void savePath() {
+
+ if (evaluationHistory.isEmpty()) {
+ throw new BugException("evaluation history is empty");
+ }
+
+ CsvOptionPanel csvOptions = new CsvOptionPanel(GeneralOptimizationDialog.class,
+ trans.get("export.header"), trans.get("export.header.ttip"));
+
+
+ JFileChooser chooser = new JFileChooser();
+ chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
+ chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
+ chooser.setAccessory(csvOptions);
+
+ if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
+ return;
+
+ File file = chooser.getSelectedFile();
+ if (file == null)
+ return;
+
+ file = FileHelper.ensureExtension(file, "csv");
+ if (!FileHelper.confirmWrite(file, this)) {
+ return;
+ }
+
+ String fieldSeparator = csvOptions.getFieldSeparator();
+ String commentCharacter = csvOptions.getCommentCharacter();
+ boolean includeHeader = csvOptions.getSelectionOption(0);
+ csvOptions.storePreferences();
+
+ log.info("Saving optimization path to " + file + ", fieldSeparator=" + fieldSeparator +
+ ", commentCharacter=" + commentCharacter + ", includeHeader=" + includeHeader);
+
+ try {
+ Writer writer = new BufferedWriter(new FileWriter(file));
+
+ // Write header
+ if (includeHeader) {
+ FunctionEvaluationData data = evaluationHistory.values().iterator().next();
+
+ writer.write(commentCharacter);
+ for (SimulationModifier mod : selectedModifiers) {
+ writer.write(mod.getRelatedObject().toString() + ": " + mod.getName() + " / " +
+ mod.getUnitGroup().getDefaultUnit().getUnit());
+ writer.write(fieldSeparator);
+ }
+ if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+ writer.write(trans.get("export.stability") + " / " + data.getDomainReference().getUnit().getUnit());
+ writer.write(fieldSeparator);
+ }
+ writer.write(getSelectedParameter().getName() + " / " +
+ getSelectedParameter().getUnitGroup().getDefaultUnit().getUnit());
+
+ writer.write("\n");
+ }
+
+
+ for (FunctionEvaluationData data : evaluationHistory.values()) {
+ Value[] state = data.getState();
+
+ for (int i = 0; i < state.length; i++) {
+ writer.write(TextUtil.doubleToString(state[i].getUnitValue()));
+ writer.write(fieldSeparator);
+ }
+
+ if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+ writer.write(TextUtil.doubleToString(data.getDomainReference().getUnitValue()));
+ writer.write(fieldSeparator);
+ }
+
+ if (data.getParameterValue() != null) {
+ writer.write(TextUtil.doubleToString(data.getParameterValue().getUnitValue()));
+ } else {
+ writer.write("N/A");
+ }
+ writer.write("\n");
+ }
+
+ writer.close();
+ log.info("File successfully saved");
+
+ } catch (IOException e) {
+ FileHelper.errorWriting(e, this);
+ }
+
+ }
+
+ /**
+ * Return the currently selected available simulation modifier from the modifier tree,
+ * or <code>null</code> if none selected.
+ */
+ private SimulationModifier getSelectedAvailableModifier() {
+ TreePath treepath = availableModifierTree.getSelectionPath();
+ if (treepath != null) {
+ Object o = ((DefaultMutableTreeNode) treepath.getLastPathComponent()).getUserObject();
+ if (o instanceof SimulationModifier) {
+ return (SimulationModifier) o;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the currently selected simulation.
+ * @return the selected simulation.
+ */
+ @SuppressWarnings("unchecked")
+ private Simulation getSelectedSimulation() {
+ return ((Named<Simulation>) simulationSelectionCombo.getSelectedItem()).get();
+ }
+
+
+ /**
+ * Return the currently selected simulation modifier from the table,
+ * or <code>null</code> if none selected.
+ * @return the selected modifier or <code>null</code>.
+ */
+ private SimulationModifier getSelectedModifier() {
+ int row = selectedModifierTable.getSelectedRow();
+ if (row < 0) {
+ return null;
+ }
+ row = selectedModifierTable.convertRowIndexToModel(row);
+ return selectedModifiers.get(row);
+ }
+
+
+ /**
+ * Return the currently selected optimization parameter.
+ * @return the selected optimization parameter.
+ */
+ @SuppressWarnings("unchecked")
+ private OptimizableParameter getSelectedParameter() {
+ return ((Named<OptimizableParameter>) optimizationParameterCombo.getSelectedItem()).get();
+ }
+
+
+ private Unit getModifierUnit(int index) {
+ return selectedModifiers.get(index).getUnitGroup().getDefaultUnit();
+ }
+
+ private String createSimulationName(String simulationName, String motorConfiguration) {
+ String name;
+ boolean hasParenthesis = motorConfiguration.matches("^[\\[\\(].*[\\]\\)]$");
+ name = simulationName + " ";
+ if (!hasParenthesis) {
+ name += "(";
+ }
+ name += motorConfiguration;
+ if (!hasParenthesis) {
+ name += ")";
+ }
+ return name;
+ }
+
+ /**
+ * The table model for the parameter selection.
+ *
+ * [Body tube: Length] [min] [max] [unit]
+ */
+ private class ParameterSelectionTableModel extends AbstractTableModel {
+
+ private static final int PARAMETER = 0;
+ private static final int CURRENT = 1;
+ private static final int MIN = 2;
+ private static final int MAX = 3;
+ private static final int COUNT = 4;
+
+ @Override
+ public int getColumnCount() {
+ return COUNT;
+ }
+
+ @Override
+ public int getRowCount() {
+ return selectedModifiers.size();
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case PARAMETER:
+ return trans.get("table.col.parameter");
+ case CURRENT:
+ return trans.get("table.col.current");
+ case MIN:
+ return trans.get("table.col.min");
+ case MAX:
+ return trans.get("table.col.max");
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+
+ }
+
+ @Override
+ public Class<?> getColumnClass(int column) {
+ switch (column) {
+ case PARAMETER:
+ return String.class;
+ case CURRENT:
+ return Double.class;
+ case MIN:
+ return Double.class;
+ case MAX:
+ return Double.class;
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+
+ SimulationModifier modifier = selectedModifiers.get(row);
+
+ switch (column) {
+ case PARAMETER:
+ return modifier.getRelatedObject().toString() + ": " + modifier.getName();
+ case CURRENT:
+ try {
+ return getModifierUnit(row).toUnit(modifier.getCurrentSIValue(getSelectedSimulation()));
+ } catch (OptimizationException e) {
+ throw new BugException("Could not read current SI value from modifier " + modifier, e);
+ }
+ case MIN:
+ return getModifierUnit(row).toUnit(modifier.getMinValue());
+ case MAX:
+ return getModifierUnit(row).toUnit(modifier.getMaxValue());
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+
+ }
+
+ @Override
+ public void setValueAt(Object value, int row, int column) {
+
+ switch (column) {
+ case PARAMETER:
+ break;
+
+ case MIN:
+ double min = (Double) value;
+ min = getModifierUnit(row).fromUnit(min);
+ selectedModifiers.get(row).setMinValue(min);
+ break;
+
+ case CURRENT:
+ break;
+
+ case MAX:
+ double max = (Double) value;
+ max = getModifierUnit(row).fromUnit(max);
+ selectedModifiers.get(row).setMaxValue(max);
+ break;
+
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+ this.fireTableRowsUpdated(row, row);
+
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ switch (column) {
+ case PARAMETER:
+ return false;
+ case CURRENT:
+ return false;
+ case MIN:
+ return true;
+ case MAX:
+ return true;
+ default:
+ throw new IndexOutOfBoundsException("column=" + column);
+ }
+ }
+
+ }
+
+
+ private class DoubleCellRenderer extends DefaultTableCellRenderer {
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+
+ double val = (Double) value;
+ Unit unit = getModifierUnit(row);
+
+ val = unit.fromUnit(val);
+ this.setText(unit.toStringUnit(val));
+
+ return this;
+ }
+ }
+
+
+ private static class SimulationModifierComparator implements Comparator<SimulationModifier> {
+
+ @Override
+ public int compare(SimulationModifier mod1, SimulationModifier mod2) {
+ Object rel1 = mod1.getRelatedObject();
+ Object rel2 = mod2.getRelatedObject();
+
+ /*
+ * Primarily order by related object:
+ *
+ * - RocketComponents first
+ * - Two RocketComponents are ordered based on their position in the rocket
+ */
+ if (!rel1.equals(rel2)) {
+
+ if (rel1 instanceof RocketComponent) {
+ if (rel2 instanceof RocketComponent) {
+
+ RocketComponent root = ((RocketComponent) rel1).getRoot();
+ for (RocketComponent c : root) {
+ if (c.equals(rel1)) {
+ return -1;
+ }
+ if (c.equals(rel2)) {
+ return 1;
+ }
+ }
+
+ throw new BugException("Error sorting modifiers, mod1=" + mod1 + " rel1=" + rel1 +
+ " mod2=" + mod2 + " rel2=" + rel2);
+
+ } else {
+ return -1;
+ }
+ } else {
+ if (rel2 instanceof RocketComponent) {
+ return 1;
+ }
+ }
+
+ }
+
+ // Secondarily sort by name
+ return collator.compare(mod1.getName(), mod2.getName());
+ }
+ }
+
}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import java.awt.Color;
+import java.awt.Paint;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.LinearInterpolator;
+import net.sf.openrocket.util.MathUtil;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.labels.CustomXYToolTipGenerator;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.PaintScale;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.chart.renderer.xy.XYShapeRenderer;
+import org.jfree.chart.title.PaintScaleLegend;
+import org.jfree.data.xy.DefaultXYZDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.ui.RectangleEdge;
+
+/**
+ * A class that plots the path of an optimization.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OptimizationPlotDialog extends JDialog {
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+
+ private static final LinearInterpolator RED = new LinearInterpolator(
+ new double[] { 0.0, 1.0 }, new double[] { 0.0, 1.0 }
+ );
+ private static final LinearInterpolator GREEN = new LinearInterpolator(
+ new double[] { 0.0, 1.0 }, new double[] { 0.0, 0.0 }
+ );
+ private static final LinearInterpolator BLUE = new LinearInterpolator(
+ new double[] { 0.0, 1.0 }, new double[] { 1.0, 0.0 }
+ );
+
+ private static final Color OUT_OF_DOMAIN_COLOR = Color.BLACK;
+
+ private static final Color PATH_COLOR = new Color(220, 0, 0);
+
+
+ public OptimizationPlotDialog(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
+ List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit, Window parent) {
+ super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
+
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ ChartPanel chart;
+ if (modifiers.size() == 1) {
+ chart = create1DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
+ } else if (modifiers.size() == 2) {
+ chart = create2DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
+ } else {
+ throw new IllegalArgumentException("Invalid dimensionality, dim=" + modifiers.size());
+ }
+ chart.setBorder(BorderFactory.createLineBorder(Color.BLACK));
+ panel.add(chart, "span, grow, wrap para");
+
+
+ JLabel label = new StyledLabel(trans.get("lbl.zoomInstructions"), -2);
+ panel.add(label, "");
+
+
+ JButton close = new JButton(trans.get("button.close"));
+ close.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ OptimizationPlotDialog.this.setVisible(false);
+ }
+ });
+ panel.add(close, "right");
+
+
+ this.add(panel);
+
+ GUIUtil.setDisposableDialogOptions(this, close);
+ }
+
+
+
+ /**
+ * Create a 1D plot of the optimization path.
+ */
+ private ChartPanel create1DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
+ List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
+
+ SimulationModifier modX = modifiers.get(0);
+ Unit xUnit = modX.getUnitGroup().getDefaultUnit();
+ Unit yUnit = parameter.getUnitGroup().getDefaultUnit();
+
+ // Create the optimization path (with autosort)
+ XYSeries series = new XYSeries(trans.get("plot1d.series"), true, true);
+ List<String> tooltips = new ArrayList<String>();
+ for (Point p : evaluations.keySet()) {
+ FunctionEvaluationData data = evaluations.get(p);
+ if (data != null) {
+ if (data.getParameterValue() != null) {
+ Value[] state = data.getState();
+ series.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(data.getParameterValue().getValue()));
+ tooltips.add(getTooltip(data, parameter));
+ }
+ } else {
+ log.error("Could not find evaluation data for point " + p);
+ }
+ }
+
+
+ String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
+ String yLabel = parameter.getName() + " / " + yUnit.getUnit();
+
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ trans.get("plot1d.title"),
+ xLabel,
+ yLabel,
+ null,
+ PlotOrientation.VERTICAL,
+ false, // Legend
+ true, // Tooltips
+ false); // Urls
+
+
+ XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
+ lineRenderer.setBaseShapesVisible(true);
+ lineRenderer.setSeriesShapesFilled(0, false);
+ //lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
+ lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
+ lineRenderer.setSeriesPaint(0, PATH_COLOR);
+ lineRenderer.setUseOutlinePaint(true);
+ CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
+ tooltipGenerator.addToolTipSeries(tooltips);
+ lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
+
+
+ XYPlot plot = chart.getXYPlot();
+
+ plot.setDataset(0, new XYSeriesCollection(series));
+ plot.setRenderer(lineRenderer);
+
+
+
+ return new ChartPanel(chart);
+ }
+
+ /**
+ * Create a 2D plot of the optimization path.
+ */
+ private ChartPanel create2DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
+ List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
+
+ Unit parameterUnit = parameter.getUnitGroup().getDefaultUnit();
+
+ SimulationModifier modX = modifiers.get(0);
+ SimulationModifier modY = modifiers.get(1);
+
+ Unit xUnit = modX.getUnitGroup().getDefaultUnit();
+ Unit yUnit = modY.getUnitGroup().getDefaultUnit();
+
+ // Create the optimization path dataset
+ XYSeries series = new XYSeries(trans.get("plot2d.path"), false, true);
+ List<String> pathTooltips = new ArrayList<String>();
+ for (Point p : path) {
+ FunctionEvaluationData data = evaluations.get(p);
+ if (data != null) {
+ Value[] state = data.getState();
+ series.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(state[1].getValue()));
+ pathTooltips.add(getTooltip(data, parameter));
+ } else {
+ log.error("Could not find evaluation data for point " + p);
+ }
+ }
+
+
+ // Create evaluations dataset
+ double min = Double.POSITIVE_INFINITY;
+ double max = Double.NEGATIVE_INFINITY;
+ double[][] evals = new double[3][evaluations.size()];
+ List<String> evalTooltips = new ArrayList<String>();
+
+ Iterator<FunctionEvaluationData> iterator = evaluations.values().iterator();
+ for (int i = 0; i < evaluations.size(); i++) {
+ FunctionEvaluationData data = iterator.next();
+ Value param = data.getParameterValue();
+ double value;
+ if (param != null) {
+ value = parameterUnit.toUnit(data.getParameterValue().getValue());
+ } else {
+ value = Double.NaN;
+ }
+
+ Value[] state = data.getState();
+ evals[0][i] = xUnit.toUnit(state[0].getValue());
+ evals[1][i] = yUnit.toUnit(state[1].getValue());
+ evals[2][i] = value;
+
+ if (value < min) {
+ min = value;
+ }
+ if (value > max) {
+ max = value;
+ }
+
+ evalTooltips.add(getTooltip(data, parameter));
+ }
+ DefaultXYZDataset evalDataset = new DefaultXYZDataset();
+ evalDataset.addSeries(trans.get("plot2d.evals"), evals);
+
+
+
+ String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
+ String yLabel = modY.getRelatedObject().toString() + ": " + modY.getName() + " / " + yUnit.getUnit();
+
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ trans.get("plot2d.title"),
+ xLabel,
+ yLabel,
+ null,
+ //evalDataset,
+ PlotOrientation.VERTICAL,
+ true, // Legend
+ true, // Tooltips
+ false); // Urls
+
+ PaintScale paintScale = new GradientScale(min, max);
+
+ XYShapeRenderer shapeRenderer = new XYShapeRenderer();
+ shapeRenderer.setPaintScale(paintScale);
+ shapeRenderer.setUseFillPaint(true);
+ CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
+ tooltipGenerator.addToolTipSeries(evalTooltips);
+ shapeRenderer.setBaseToolTipGenerator(tooltipGenerator);
+
+
+ shapeRenderer.getLegendItem(0, 0);
+
+
+ XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
+ lineRenderer.setBaseShapesVisible(true);
+ lineRenderer.setSeriesShapesFilled(0, false);
+ lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
+ lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
+ lineRenderer.setSeriesPaint(0, PATH_COLOR);
+ lineRenderer.setUseOutlinePaint(true);
+ tooltipGenerator = new CustomXYToolTipGenerator();
+ tooltipGenerator.addToolTipSeries(pathTooltips);
+ lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
+
+
+ XYPlot plot = chart.getXYPlot();
+
+ plot.setDataset(0, new XYSeriesCollection(series));
+ plot.setRenderer(lineRenderer);
+
+ plot.setDataset(1, evalDataset);
+ plot.setRenderer(1, shapeRenderer);
+
+
+ // Add value scale
+ NumberAxis numberAxis = new NumberAxis(parameter.getName() + " / " + parameterUnit.getUnit());
+ PaintScaleLegend scale = new PaintScaleLegend(paintScale, numberAxis);
+ scale.setPosition(RectangleEdge.RIGHT);
+ scale.setMargin(4.0D, 4.0D, 40.0D, 4.0D);
+ scale.setAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
+ chart.addSubtitle(scale);
+
+
+ return new ChartPanel(chart);
+ }
+
+
+
+ private String getTooltip(FunctionEvaluationData data, OptimizableParameter parameter) {
+ String ttip = "<html>";
+ if (data.getParameterValue() != null) {
+ ttip += parameter.getName() + ": " +
+ parameter.getUnitGroup().getDefaultUnit().toStringUnit(data.getParameterValue().getValue());
+ ttip += "<br>";
+ }
+ if (data.getDomainReference() != null) {
+ ttip += trans.get("plot.ttip.stability") + " " + data.getDomainReference();
+ }
+ return ttip;
+ }
+
+ private class GradientScale implements PaintScale {
+
+ private final double min;
+ private final double max;
+
+ public GradientScale(double min, double max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ @Override
+ public Paint getPaint(double value) {
+ if (Double.isNaN(value)) {
+ return OUT_OF_DOMAIN_COLOR;
+ }
+
+ value = MathUtil.map(value, min, max, 0.0, 1.0);
+ value = MathUtil.clamp(value, 0.0, 1.0);
+
+ float r = (float) RED.getValue(value);
+ float g = (float) GREEN.getValue(value);
+ float b = (float) BLUE.getValue(value);
+
+ return new Color(r, g, b);
+ }
+
+ @Override
+ public double getLowerBound() {
+ return min;
+ }
+
+ @Override
+ public double getUpperBound() {
+ return max;
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import net.sf.openrocket.optimization.general.Point;
+
+/**
+ * Value object for optimization step data.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OptimizationStepData {
+
+ private final Point oldPoint;
+ private final double oldValue;
+ private final Point newPoint;
+ private final double newValue;
+ private final double stepSize;
+
+
+ public OptimizationStepData(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
+ this.oldPoint = oldPoint;
+ this.oldValue = oldValue;
+ this.newPoint = newPoint;
+ this.newValue = newValue;
+ this.stepSize = stepSize;
+ }
+
+
+ public Point getOldPoint() {
+ return oldPoint;
+ }
+
+
+ public double getOldValue() {
+ return oldValue;
+ }
+
+
+ public Point getNewPoint() {
+ return newPoint;
+ }
+
+
+ public double getNewValue() {
+ return newValue;
+ }
+
+
+ public double getStepSize() {
+ return stepSize;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import javax.swing.SwingUtilities;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.logging.LogHelper;
+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.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.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationFunction;
+import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationListener;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.Value;
+import net.sf.openrocket.util.BugException;
+
+/**
+ * A background worker that runs the optimization in the background. It supports providing
+ * evaluation and step counter information via listeners that are executed on the EDT.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class OptimizationWorker extends Thread implements OptimizationController, RocketOptimizationListener {
+
+ /*
+ * Note: This is implemented as a separate Thread object instead of a SwingWorker because
+ * the SwingWorker cannot be interrupted in any way except by canceling the task, which
+ * makes it impossible to wait for its exiting (SwingWorker.get() throws a CancellationException
+ * if cancel() has been called).
+ *
+ * SwingWorker also seems to miss some chunks that have been provided to process() when the
+ * thread ends.
+ *
+ * Nothing of this is documented, of course...
+ */
+
+ private static final LogHelper log = Application.getLogger();
+
+ /** Notify listeners every this many milliseconds */
+ private static final long PURGE_TIMEOUT = 500;
+ /** End optimization when step size is below this threshold */
+ private static final double STEP_SIZE_LIMIT = 0.005;
+
+ private final FunctionOptimizer optimizer;
+ private final RocketOptimizationFunction function;
+
+ private final Simulation simulation;
+ private final SimulationModifier[] modifiers;
+
+ private final ParallelFunctionCache cache;
+
+
+ private final LinkedBlockingQueue<FunctionEvaluationData> evaluationQueue =
+ new LinkedBlockingQueue<FunctionEvaluationData>();
+ private final LinkedBlockingQueue<OptimizationStepData> stepQueue =
+ new LinkedBlockingQueue<OptimizationStepData>();
+ private volatile long lastPurge = 0;
+
+ private OptimizationException optimizationException = null;
+
+
+ /**
+ * Sole constructor
+ * @param simulation the simulation
+ * @param parameter the optimization parameter
+ * @param goal the optimization goal
+ * @param domain the optimization domain
+ * @param modifiers the simulation modifiers
+ */
+ public OptimizationWorker(Simulation simulation, OptimizableParameter parameter,
+ OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
+
+ this.simulation = simulation;
+ this.modifiers = modifiers.clone();
+
+ function = new RocketOptimizationFunction(simulation, parameter, goal, domain, modifiers);
+ function.addRocketOptimizationListener(this);
+
+ cache = new ParallelExecutorCache(1);
+ cache.setFunction(function);
+
+ optimizer = new MultidirectionalSearchOptimizer(cache);
+ }
+
+
+ @Override
+ public void run() {
+ try {
+
+ double[] current = new double[modifiers.length];
+ for (int i = 0; i < modifiers.length; i++) {
+ current[i] = modifiers[i].getCurrentScaledValue(simulation);
+ }
+ Point initial = new Point(current);
+
+ optimizer.optimize(initial, this);
+
+ } catch (OptimizationException e) {
+ this.optimizationException = e;
+ } finally {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ lastPurge = System.currentTimeMillis() + 24L * 3600L * 1000L;
+ processQueue();
+ done(optimizationException);
+ }
+ });
+ }
+ }
+
+ /**
+ * This method is called after the optimization has ended, either normally, when interrupted
+ * or by throwing an exception. This method is called on the EDT, like the done() method of SwingWorker.
+ * <p>
+ * All data chunks to the listeners will be guaranteed to have been processed before calling done().
+ *
+ * @param exception a possible optimization exception that occurred, or <code>null</code> for normal exit.
+ */
+ protected abstract void done(OptimizationException exception);
+
+
+ /**
+ * This method is called for each function evaluation that has taken place.
+ * This method is called on the EDT.
+ *
+ * @param data the data accumulated since the last call
+ */
+ protected abstract void functionEvaluated(List<FunctionEvaluationData> data);
+
+ /**
+ * This method is called after each step taken by the optimization algorithm.
+ * This method is called on the EDT.
+ *
+ * @param data the data accumulated since the last call
+ */
+ protected abstract void optimizationStepTaken(List<OptimizationStepData> data);
+
+
+ /**
+ * Publishes data to the listeners. The queue is purged every PURGE_TIMEOUT milliseconds.
+ *
+ * @param data the data to publish to the listeners
+ */
+ private synchronized void publish(FunctionEvaluationData evaluation, OptimizationStepData step) {
+
+ if (evaluation != null) {
+ evaluationQueue.add(evaluation);
+ }
+ if (step != null) {
+ stepQueue.add(step);
+ }
+
+ // Add a method to the EDT to process the queue data
+ long now = System.currentTimeMillis();
+ if (lastPurge + PURGE_TIMEOUT <= now) {
+ lastPurge = now;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ processQueue();
+ }
+ });
+ }
+
+ }
+
+
+ /**
+ * Process the queue and call the listeners. This method must always be called from the EDT.
+ */
+ private void processQueue() {
+
+ if (!SwingUtilities.isEventDispatchThread()) {
+ throw new BugException("processQueue called from non-EDT");
+ }
+
+
+ List<FunctionEvaluationData> evaluations = new ArrayList<FunctionEvaluationData>();
+ evaluationQueue.drainTo(evaluations);
+ if (!evaluations.isEmpty()) {
+ functionEvaluated(evaluations);
+ }
+
+
+ List<OptimizationStepData> steps = new ArrayList<OptimizationStepData>();
+ stepQueue.drainTo(steps);
+ if (!steps.isEmpty()) {
+ optimizationStepTaken(steps);
+ }
+ }
+
+
+
+
+ /*
+ * NOTE: The stepTaken and evaluated methods may be called from other
+ * threads than the EDT or the SwingWorker thread!
+ */
+
+ @Override
+ public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
+ publish(null, new OptimizationStepData(oldPoint, oldValue, newPoint, newValue, stepSize));
+
+ if (stepSize < STEP_SIZE_LIMIT) {
+ log.info("stepSize=" + stepSize + " is below limit, ending optimization");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
+ publish(new FunctionEvaluationData(point, state, domainReference, parameterValue, goalValue), null);
+ }
+
+}
import java.util.Map;
import javax.swing.JTree;
+import javax.swing.ToolTipManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import net.sf.openrocket.gui.components.BasicTree;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.TextUtil;
/**
* A tree that displays the simulation modifiers in a tree structure.
*/
public class SimulationModifierTree extends BasicTree {
+ private final List<SimulationModifier> selectedModifiers;
+
/**
* Sole constructor.
*
* @param rocket the rocket.
* @param simulationModifiers the simulation modifiers, ordered and mapped by components
+ * @param selectedModifiers a list of the currently selected modifiers (may be modified).
*/
- public SimulationModifierTree(Rocket rocket, Map<Object, List<SimulationModifier>> simulationModifiers) {
- super(createModifierTree(rocket, simulationModifiers));
+ public SimulationModifierTree(Rocket rocket, Map<Object, List<SimulationModifier>> simulationModifiers,
+ List<SimulationModifier> selectedModifiers) {
+ this.selectedModifiers = selectedModifiers;
+ populateTree(rocket, simulationModifiers);
this.setCellRenderer(new ComponentModifierTreeRenderer());
+
+ // Enable tooltips for this component
+ ToolTipManager.sharedInstance().registerComponent(this);
+
expandComponents();
}
-
- private static DefaultMutableTreeNode createModifierTree(Rocket rocket,
- Map<Object, List<SimulationModifier>> simulationModifiers) {
+ /**
+ * Populate the simulation modifier tree from the provided information. This can be used to update
+ * the tree.
+ */
+ public void populateTree(Rocket rocket, Map<Object, List<SimulationModifier>> simulationModifiers) {
DefaultMutableTreeNode baseNode = new DefaultMutableTreeNode(rocket);
populateTree(baseNode, rocket, simulationModifiers);
- return baseNode;
+ this.setModel(new DefaultTreeModel(baseNode));
}
}
+ /**
+ * Expand the rocket components, but not the modifiers.
+ */
@SuppressWarnings("rawtypes")
- private void expandComponents() {
+ public void expandComponents() {
DefaultMutableTreeNode baseNode = (DefaultMutableTreeNode) this.getModel().getRoot();
Enumeration enumeration = baseNode.breadthFirstEnumeration();
if (object instanceof RocketComponent) {
setForeground(Color.GRAY);
setFont(componentFont);
+
+ // Set tooltip
+ RocketComponent c = (RocketComponent) object;
+ String comment = c.getComment().trim();
+ if (comment.length() > 0) {
+ comment = TextUtil.htmlEncode(comment);
+ comment = "<html>" + comment.replace("\n", "<br>");
+ this.setToolTipText(comment);
+ } else {
+ this.setToolTipText(null);
+ }
} else if (object instanceof String) {
setForeground(Color.GRAY);
setFont(stringFont);
} else if (object instanceof SimulationModifier) {
- setForeground(Color.BLACK);
+
+ if (selectedModifiers.contains(object)) {
+ setForeground(Color.GRAY);
+ } else {
+ setForeground(Color.BLACK);
+ }
setFont(modifierFont);
setText(((SimulationModifier) object).getName());
+ setToolTipText(((SimulationModifier) object).getDescription());
}
return this;
import java.util.concurrent.ExecutionException;
import javax.swing.Action;
+import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import net.sf.openrocket.gui.dialogs.optimization.GeneralOptimizationDialog;
import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
import net.sf.openrocket.gui.main.componenttree.ComponentTree;
-import net.sf.openrocket.gui.optimization.OptimizationTestDialog;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
scroll.setBorder(null);
scroll.setViewportBorder(null);
- TitledBorder border = new TitledBorder(trans.get("BasicFrame.title.Addnewcomp"));
- border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD));
+ TitledBorder border = BorderFactory.createTitledBorder(trans.get("BasicFrame.title.Addnewcomp"));
+ GUIUtil.changeFontStyle(border, Font.BOLD);
scroll.setBorder(border);
panel.add(scroll, "grow");
menu.add(item);
+ item = new JMenuItem(trans.get("main.menu.analyze.optimization"), KeyEvent.VK_O);
+ item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.optimization.desc"));
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Rocket optimization selected");
+ new GeneralOptimizationDialog(document, BasicFrame.this).setVisible(true);
+ }
+ });
+ menu.add(item);
+
+
+
//// Debug
// (shown if openrocket.debug.menu is defined)
if (System.getProperty("openrocket.debug.menu") != null) {
menu.add(item);
- item = new JMenuItem("General optimization test");
- item.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- new GeneralOptimizationDialog(document, BasicFrame.this).setVisible(true);
- }
- });
- menu.add(item);
-
-
- item = new JMenuItem("Optimization test");
- item.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- new OptimizationTestDialog(BasicFrame.this, document).setVisible(true);
- }
- });
- menu.add(item);
-
return menu;
}
*
*/
public void printAction() {
- if (!Prefs.getBoolean("printing.experimental.communicated", false)) {
- log.info("Showing printing is experimental warning to the user");
- JOptionPane.showMessageDialog(this, "Printing is an currently an experimental feature " +
- "and might not fully work on all platforms",
- "Experimental feature", JOptionPane.WARNING_MESSAGE);
- Prefs.putBoolean("printing.experimental.communicated", true);
- }
new PrintDialog(this, document).setVisible(true);
}
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.RK4SimulationStepper;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
private final Window parentWindow;
private final Simulation simulation;
- private final GUISimulationConditions conditions;
+ private final SimulationOptions conditions;
private final Configuration configuration;
private static final Translator trans = Application.getTranslator();
this.parentWindow = parent;
this.simulation = s;
- this.conditions = simulation.getConditions();
+ this.conditions = simulation.getOptions();
configuration = simulation.getConfiguration();
JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]"));
sub.add(label);
m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE,
- 0, GUISimulationConditions.MAX_LAUNCH_ROD_ANGLE);
+ 0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
unit.setToolTipText(tip);
sub.add(unit,"growx");
slider = new BasicSlider(m.getSliderModel(0, Math.PI/9,
- GUISimulationConditions.MAX_LAUNCH_ROD_ANGLE));
+ SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
slider.setToolTipText(tip);
sub.add(slider,"w 75lp, wrap");
public class SimulationRunDialog extends JDialog {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
/** Update the dialog status every this many ms */
private static final long UPDATE_MS = 200;
this.simulations = simulations;
+
+ // Randomize the simulation random seeds
+ for (Simulation sim : simulations) {
+ sim.getOptions().randomizeSeed();
+ }
+
// Initialize the simulations
int n = simulations.length;
simulationNames = new String[n];
panel.add(altLabel, "growx, wrap rel");
//// Velocity:
- panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Velocity") +" "));
+ panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Velocity") + " "));
velLabel = new JLabel("");
panel.add(velLabel, "growx, wrap para");
double launchBurn = 0;
double otherBurn = 0;
Configuration config = simulation.getConfiguration();
- String id = simulation.getConditions().getMotorConfigurationID();
+ String id = simulation.getOptions().getMotorConfigurationID();
Iterator<MotorMount> iterator = config.motorIterator();
while (iterator.hasNext()) {
MotorMount m = iterator.next();
DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
new Object[] {
//// An exception occurred during the simulation:
- trans.get("SimuRunDlg.msg.AnException1"),
+ trans.get("SimuRunDlg.msg.AnException1"),
t.getMessage(),
simulation.getSimulationListeners().isEmpty() ?
trans.get("SimuRunDlg.msg.AnException2") : ""
t.printStackTrace();
DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
new Object[] {
- //// A computation error occurred during the simulation.
- trans.get("SimuRunDlg.msg.AssertionError1"),
- //// Please report this as a bug along with the details below.
- trans.get("SimuRunDlg.msg.AssertionError2")
+ //// A computation error occurred during the simulation.
+ trans.get("SimuRunDlg.msg.AssertionError1"),
+ //// Please report this as a bug along with the details below.
+ trans.get("SimuRunDlg.msg.AssertionError2")
},
stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
new Object[] {
//// An unknown error was encountered during the simulation.
- trans.get("SimuRunDlg.msg.unknownerror1"),
+ trans.get("SimuRunDlg.msg.unknownerror1"),
//// The program may be unstable, you should save all your designs and restart OpenRocket now!
- trans.get("SimuRunDlg.msg.unknownerror2")
+ trans.get("SimuRunDlg.msg.unknownerror2")
},
stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
+++ /dev/null
-package net.sf.openrocket.gui.optimization;
-
-import static net.sf.openrocket.util.MathUtil.pow2;
-
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JPanel;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.optimization.general.Function;
-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.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.rocketoptimization.RocketOptimizationFunction;
-import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
-import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
-import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
-import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
-import net.sf.openrocket.optimization.rocketoptimization.modifiers.GenericComponentModifier;
-import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumAltitudeParameter;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.GUIUtil;
-
-public class OptimizationTestDialog extends JDialog {
-
- private final OpenRocketDocument document;
-
- public OptimizationTestDialog(Window parent, OpenRocketDocument document) {
- super(parent, "Optimization", ModalityType.APPLICATION_MODAL);
-
- this.document = document;
-
- JPanel panel = new JPanel(new MigLayout("fill"));
- this.add(panel);
-
- JButton button = new JButton("Test optimize");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- try {
- doOptimize();
- } catch (OptimizationException e1) {
- e1.printStackTrace();
- }
- }
- });
- panel.add(button, "wrap para");
-
-
-
- JButton close = new JButton("Close");
- close.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- OptimizationTestDialog.this.dispose();
- }
- });
- panel.add(close);
-
-
- GUIUtil.setDisposableDialogOptions(this, close);
- }
-
-
- private void doOptimize() throws OptimizationException {
- Simulation sim = document.getSimulation(0);
- Rocket rocket = sim.getRocket();
-
- RocketComponent body = null;
-
- for (RocketComponent c : rocket) {
- if (c instanceof BodyTube) {
- body = c;
- break;
- }
- }
-
- Point initial;
-
- SimulationDomain domain;
- // domain= new IdentitySimulationDomain();
- domain = new StabilityDomain(2, false);
-
- SimulationModifier mod1 = new GenericComponentModifier("Test", body,
- UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, body.getID(), "Length");
- mod1.setMinValue(0.1);
- mod1.setMaxValue(0.7);
-
- SimulationModifier mod2 = new GenericComponentModifier("Test", body,
- UnitGroup.UNITS_LENGTH, 2.0, BodyTube.class, body.getID(), "OuterRadius");
- mod2.setMinValue(0.01);
- mod2.setMaxValue(0.10);
-
- OptimizationController controller = new OptimizationController() {
- int step = 0;
-
- @Override
- public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
- step++;
- System.out.println("STEP " + step + " oldValue=" + oldValue + " newValue=" + newValue +
- " oldPoint=" + oldPoint + " newPoint=" + newPoint +
- " stepSize=" + stepSize);
- return step < 20;
- }
- };
-
-
- initial = new Point(mod1.getCurrentScaledValue(sim), mod2.getCurrentScaledValue(sim));
-
-
- Function function = new RocketOptimizationFunction(sim, new MaximumAltitudeParameter(),
- new MaximizationGoal(), domain, mod1, mod2);
- /*
- function = new Function() {
- @Override
- public double evaluate(Point point) throws InterruptedException, OptimizationException {
- // y = ax^2 + bx + c
- // y' = 2ax + b
- // 2a * pi/4 + b = 0
- // b = -a*pi/2
- // a=-1 -> b = pi/2
-
-
- double x = point.get(0);
- double y = -x * x + Math.PI / 2 * x;
- System.out.println("Evaluating at x=" + x + " value=" + y);
- return y;
- }
- };
- */
-
- ParallelFunctionCache cache = new ParallelExecutorCache(1);
- cache.setFunction(function);
-
- FunctionOptimizer optimizer = new MultidirectionalSearchOptimizer(cache);
-
- optimizer.optimize(initial, controller);
- }
-
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-
- private static int evalCount = 0;
-
- public static void main(String[] args) throws OptimizationException {
- Point initial;
-
-
-
- OptimizationController controller = new OptimizationController() {
- int step = 0;
-
- @Override
- public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
- step++;
- System.out.println("STEP " + step + " oldValue=" + oldValue + " newValue=" + newValue +
- " oldPoint=" + oldPoint + " newPoint=" + newPoint +
- " stepSize=" + stepSize);
- return step < 20;
- }
- };
-
-
- initial = new Point(0.5, 0.5);
-
-
- Function function = new Function() {
- @Override
- public double evaluate(Point point) throws InterruptedException, OptimizationException {
- // y = ax^2 + bx + c
- // y' = 2ax + b
- // 2a * pi/4 + b = 0
- // b = -a*pi/2
- // a=-1 -> b = pi/2
-
- evalCount++;
-
- // double x = point.get(0);
- // double y = x * x - Math.PI / 2 * x;
- // System.out.println("Evaluating at x=" + x + " value=" + y);
- // return y;
-
- double x = point.get(0);
- double y = point.get(1);
- double z = 4 * pow2((x - 0.3231)) + 2 * pow2(y - 0.8923);
-
- System.out.println("Evaluation " + evalCount + ": x=" + x + " y=" + y + " z=" + z);
-
- return z;
- }
- };
-
-
- ParallelFunctionCache cache = new ParallelExecutorCache();
- cache.setFunction(function);
-
- FunctionOptimizer optimizer = new MultidirectionalSearchOptimizer(cache);
-
- optimizer.optimize(initial, controller);
-
-
- System.out.println("Total evaluation count: " + evalCount);
- }
-}
public class PlotConfiguration implements Cloneable {
private static final Translator trans = Application.getTranslator();
-
+
public static final PlotConfiguration[] DEFAULT_CONFIGURATIONS;
static {
ArrayList<PlotConfiguration> configs = new ArrayList<PlotConfiguration>();
continue;
double d = (max - min) / axis.getRangeLength();
- d = Math.sqrt(d); // Prioritize small ranges
+ d = MathUtil.safeSqrt(d); // Prioritize small ranges
goodness += d * 100.0;
}
+++ /dev/null
-package net.sf.openrocket.gui.plot;
-
-import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Composite;
-import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.geom.Line2D;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-import javax.imageio.ImageIO;
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.StyledLabel;
-import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.simulation.FlightDataBranch;
-import net.sf.openrocket.simulation.FlightDataType;
-import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.unit.Unit;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.GUIUtil;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Prefs;
-
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.annotations.XYImageAnnotation;
-import org.jfree.chart.axis.NumberAxis;
-import org.jfree.chart.axis.ValueAxis;
-import org.jfree.chart.plot.Marker;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.chart.plot.ValueMarker;
-import org.jfree.chart.plot.XYPlot;
-import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
-import org.jfree.chart.title.TextTitle;
-import org.jfree.data.Range;
-import org.jfree.data.xy.XYSeries;
-import org.jfree.data.xy.XYSeriesCollection;
-import org.jfree.text.TextUtilities;
-import org.jfree.ui.LengthAdjustmentType;
-import org.jfree.ui.RectangleAnchor;
-import org.jfree.ui.TextAnchor;
-
-public class PlotDialog extends JDialog {
-
- private static final float PLOT_STROKE_WIDTH = 1.5f;
- private static final Translator trans = Application.getTranslator();
-
- private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
- private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
- new HashMap<FlightEvent.Type, Color>();
- static {
- EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255, 0, 0));
- EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0, 80, 196));
- EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0, 100, 80));
- EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230, 130, 15));
- EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80, 55, 40));
- EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80, 55, 40));
- EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80, 55, 40));
- EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15, 120, 15));
- EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0, 0, 128));
- EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0, 0, 0));
- EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
- }
-
- private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
- new HashMap<FlightEvent.Type, Image>();
- static {
- loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
- loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
- loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
- loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
- loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
- loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
- loadImage(FlightEvent.Type.STAGE_SEPARATION,
- "pix/eventicons/event-stage-separation.png");
- loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
- loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
- "pix/eventicons/event-recovery-device-deployment.png");
- loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
- loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
- }
-
- private static void loadImage(FlightEvent.Type type, String file) {
- InputStream is;
-
- is = ClassLoader.getSystemResourceAsStream(file);
- if (is == null) {
- System.out.println("ERROR: File " + file + " not found!");
- return;
- }
-
- try {
- Image image = ImageIO.read(is);
- EVENT_IMAGES.put(type, image);
- } catch (IOException ignore) {
- ignore.printStackTrace();
- }
- }
-
-
-
-
- private final List<ModifiedXYItemRenderer> renderers =
- new ArrayList<ModifiedXYItemRenderer>();
-
- private PlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
- //// Flight data plot
- super(parent, trans.get("PlotDialog.title.Flightdataplot"));
- this.setModalityType(ModalityType.DOCUMENT_MODAL);
-
- final boolean initialShowPoints = Prefs.getBoolean(Prefs.PLOT_SHOW_POINTS, false);
-
-
- // Fill the auto-selections
- FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
- PlotConfiguration filled = config.fillAutoAxes(branch);
- List<Axis> axes = filled.getAllAxes();
-
-
- // Create the data series for both axes
- XYSeriesCollection[] data = new XYSeriesCollection[2];
- data[0] = new XYSeriesCollection();
- data[1] = new XYSeriesCollection();
-
-
- // Get the domain axis type
- final FlightDataType domainType = filled.getDomainAxisType();
- final Unit domainUnit = filled.getDomainAxisUnit();
- if (domainType == null) {
- throw new IllegalArgumentException("Domain axis type not specified.");
- }
- List<Double> x = branch.get(domainType);
-
-
- // Get plot length (ignore trailing NaN's)
- int typeCount = filled.getTypeCount();
- int dataLength = 0;
- for (int i = 0; i < typeCount; i++) {
- FlightDataType type = filled.getType(i);
- List<Double> y = branch.get(type);
-
- for (int j = dataLength; j < y.size(); j++) {
- if (!Double.isNaN(y.get(j)) && !Double.isInfinite(y.get(j)))
- dataLength = j;
- }
- }
- dataLength = Math.min(dataLength, x.size());
-
-
- // Create the XYSeries objects from the flight data and store into the collections
- String[] axisLabel = new String[2];
- for (int i = 0; i < typeCount; i++) {
- // Get info
- FlightDataType type = filled.getType(i);
- Unit unit = filled.getUnit(i);
- int axis = filled.getAxis(i);
- String name = getLabel(type, unit);
-
- // Store data in provided units
- List<Double> y = branch.get(type);
- XYSeries series = new XYSeries(name, false, true);
- for (int j = 0; j < dataLength; j++) {
- series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
- }
- data[axis].addSeries(series);
-
- // Update axis label
- if (axisLabel[axis] == null)
- axisLabel[axis] = type.getName();
- else
- axisLabel[axis] += "; " + type.getName();
- }
-
-
- // Create the chart using the factory to get all default settings
- JFreeChart chart = ChartFactory.createXYLineChart(
- //// Simulated flight
- trans.get("PlotDialog.Chart.Simulatedflight"),
- null,
- null,
- null,
- PlotOrientation.VERTICAL,
- true,
- true,
- false
- );
-
- chart.addSubtitle(new TextTitle(config.getName()));
-
- // Add the data and formatting to the plot
- XYPlot plot = chart.getXYPlot();
- int axisno = 0;
- for (int i = 0; i < 2; i++) {
- // Check whether axis has any data
- if (data[i].getSeriesCount() > 0) {
- // Create and set axis
- double min = axes.get(i).getMinValue();
- double max = axes.get(i).getMaxValue();
- NumberAxis axis = new PresetNumberAxis(min, max);
- axis.setLabel(axisLabel[i]);
- // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
- plot.setRangeAxis(axisno, axis);
-
- // Add data and map to the axis
- plot.setDataset(axisno, data[i]);
- ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
- r.setBaseShapesVisible(initialShowPoints);
- r.setBaseShapesFilled(true);
- for (int j = 0; j < data[i].getSeriesCount(); j++) {
- r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
- }
- renderers.add(r);
- plot.setRenderer(axisno, r);
- plot.mapDatasetToRangeAxis(axisno, axisno);
- axisno++;
- }
- }
-
- plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
- plot.addDomainMarker(new ValueMarker(0));
- plot.addRangeMarker(new ValueMarker(0));
-
-
-
- // Create list of events to show (combine event too close to each other)
- ArrayList<Double> timeList = new ArrayList<Double>();
- ArrayList<String> eventList = new ArrayList<String>();
- ArrayList<Color> colorList = new ArrayList<Color>();
- ArrayList<Image> imageList = new ArrayList<Image>();
-
- HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
-
- double prevTime = -100;
- String text = null;
- Color color = null;
- Image image = null;
-
- List<FlightEvent> events = branch.getEvents();
- for (int i = 0; i < events.size(); i++) {
- FlightEvent event = events.get(i);
- double t = event.getTime();
- FlightEvent.Type type = event.getType();
-
- if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
- if (Math.abs(t - prevTime) <= 0.01) {
-
- if (!typeSet.contains(type)) {
- text = text + ", " + type.toString();
- color = getEventColor(type);
- image = EVENT_IMAGES.get(type);
- typeSet.add(type);
- }
-
- } else {
-
- if (text != null) {
- timeList.add(prevTime);
- eventList.add(text);
- colorList.add(color);
- imageList.add(image);
- }
- prevTime = t;
- text = type.toString();
- color = getEventColor(type);
- image = EVENT_IMAGES.get(type);
- typeSet.clear();
- typeSet.add(type);
-
- }
- }
- }
- if (text != null) {
- timeList.add(prevTime);
- eventList.add(text);
- colorList.add(color);
- imageList.add(image);
- }
-
-
- // Create the event markers
-
- if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
-
- // Domain time is plotted as vertical markers
- for (int i = 0; i < eventList.size(); i++) {
- double t = timeList.get(i);
- String event = eventList.get(i);
- color = colorList.get(i);
-
- ValueMarker m = new ValueMarker(t);
- m.setLabel(event);
- m.setPaint(color);
- m.setLabelPaint(color);
- m.setAlpha(0.7f);
- plot.addDomainMarker(m);
- }
-
- } else {
-
- // Other domains are plotted as image annotations
- List<Double> time = branch.get(FlightDataType.TYPE_TIME);
- List<Double> domain = branch.get(config.getDomainAxisType());
-
- for (int i = 0; i < eventList.size(); i++) {
- final double t = timeList.get(i);
- String event = eventList.get(i);
- image = imageList.get(i);
-
- if (image == null)
- continue;
-
- // Calculate index and interpolation position a
- final double a;
- int tindex = Collections.binarySearch(time, t);
- if (tindex < 0) {
- tindex = -tindex - 1;
- }
- if (tindex >= time.size()) {
- // index greater than largest value in time list
- tindex = time.size() - 1;
- a = 0;
- } else if (tindex <= 0) {
- // index smaller than smallest value in time list
- tindex = 0;
- a = 0;
- } else {
- tindex--;
- double t1 = time.get(tindex);
- double t2 = time.get(tindex + 1);
-
- if ((t1 > t) || (t2 < t)) {
- throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
- }
-
- if (MathUtil.equals(t1, t2)) {
- a = 0;
- } else {
- a = 1 - (t - t1) / (t2 - t1);
- }
- }
-
- double xcoord;
- if (a == 0) {
- xcoord = domain.get(tindex);
- } else {
- xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
- }
-
- for (int index = 0; index < config.getTypeCount(); index++) {
- FlightDataType type = config.getType(index);
- List<Double> range = branch.get(type);
-
- // Image annotations are not supported on the right-side axis
- // TODO: LOW: Can this be achieved by JFreeChart?
- if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
- continue;
- }
-
- double ycoord;
- if (a == 0) {
- ycoord = range.get(tindex);
- } else {
- ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
- }
-
- // Convert units
- xcoord = config.getDomainAxisUnit().toUnit(xcoord);
- ycoord = config.getUnit(index).toUnit(ycoord);
-
- XYImageAnnotation annotation =
- new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
- annotation.setToolTipText(event);
- plot.addAnnotation(annotation);
- }
- }
- }
-
-
- // Create the dialog
-
- JPanel panel = new JPanel(new MigLayout("fill"));
- this.add(panel);
-
- ChartPanel chartPanel = new ChartPanel(chart,
- false, // properties
- true, // save
- false, // print
- true, // zoom
- true); // tooltips
- chartPanel.setMouseWheelEnabled(true);
- chartPanel.setEnforceFileExtensions(true);
- chartPanel.setInitialDelay(500);
-
- chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
-
- panel.add(chartPanel, "grow, wrap 20lp");
-
- //// Show data points
- final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
- check.setSelected(initialShowPoints);
- check.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- boolean show = check.isSelected();
- Prefs.putBoolean(Prefs.PLOT_SHOW_POINTS, show);
- for (ModifiedXYItemRenderer r : renderers) {
- r.setBaseShapesVisible(show);
- }
- }
- });
- panel.add(check, "split, left");
-
-
- JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
- panel.add(label, "gapleft para");
-
-
- panel.add(new JPanel(), "growx");
-
- //// Close button
- JButton button = new JButton(trans.get("dlg.but.close"));
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- PlotDialog.this.dispose();
- }
- });
- panel.add(button, "right");
-
- this.setLocationByPlatform(true);
- this.pack();
-
- GUIUtil.setDisposableDialogOptions(this, button);
- }
-
- private String getLabel(FlightDataType type, Unit unit) {
- String name = type.getName();
- if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
- !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
- name += " (" + unit.getUnit() + ")";
- return name;
- }
-
-
-
- private class PresetNumberAxis extends NumberAxis {
- private final double min;
- private final double max;
-
- public PresetNumberAxis(double min, double max) {
- this.min = min;
- this.max = max;
- autoAdjustRange();
- }
-
- @Override
- protected void autoAdjustRange() {
- this.setRange(min, max);
- }
- }
-
-
- /**
- * Static method that shows a plot with the specified parameters.
- *
- * @param parent the parent window, which will be blocked.
- * @param simulation the simulation to plot.
- * @param config the configuration of the plot.
- */
- public static void showPlot(Window parent, Simulation simulation, PlotConfiguration config) {
- new PlotDialog(parent, simulation, config).setVisible(true);
- }
-
-
-
- private static Color getEventColor(FlightEvent.Type type) {
- Color c = EVENT_COLORS.get(type);
- if (c != null)
- return c;
- return DEFAULT_EVENT_COLOR;
- }
-
-
-
-
-
- /**
- * A modification to the standard renderer that renders the domain marker
- * labels vertically instead of horizontally.
- */
- private static class ModifiedXYItemRenderer extends StandardXYItemRenderer {
-
- @Override
- public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
- Marker marker, Rectangle2D dataArea) {
-
- if (!(marker instanceof ValueMarker)) {
- // Use parent for all others
- super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
- return;
- }
-
- /*
- * Draw the normal marker, but with rotated text.
- * Copied from the overridden method.
- */
- ValueMarker vm = (ValueMarker) marker;
- double value = vm.getValue();
- Range range = domainAxis.getRange();
- if (!range.contains(value)) {
- return;
- }
-
- double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
-
- PlotOrientation orientation = plot.getOrientation();
- Line2D line = null;
- if (orientation == PlotOrientation.HORIZONTAL) {
- line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
- } else {
- line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
- }
-
- final Composite originalComposite = g2.getComposite();
- g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
- .getAlpha()));
- g2.setPaint(marker.getPaint());
- g2.setStroke(marker.getStroke());
- g2.draw(line);
-
- String label = marker.getLabel();
- RectangleAnchor anchor = marker.getLabelAnchor();
- if (label != null) {
- Font labelFont = marker.getLabelFont();
- g2.setFont(labelFont);
- g2.setPaint(marker.getLabelPaint());
- Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
- orientation, dataArea, line.getBounds2D(), marker
- .getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
-
- // Changed:
- TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
- TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
- (float) coordinates.getY(), textAnchor,
- -Math.PI / 2, textAnchor);
- }
- g2.setComposite(originalComposite);
- }
-
- }
-
-}
--- /dev/null
+package net.sf.openrocket.gui.plot;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.annotations.XYImageAnnotation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.Marker;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.ValueMarker;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.Range;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.text.TextUtilities;
+import org.jfree.ui.LengthAdjustmentType;
+import org.jfree.ui.RectangleAnchor;
+import org.jfree.ui.TextAnchor;
+
+/**
+ * Dialog that shows a plot of a simulation results based on user options.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SimulationPlotDialog extends JDialog {
+
+ private static final float PLOT_STROKE_WIDTH = 1.5f;
+ private static final Translator trans = Application.getTranslator();
+
+ private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
+ private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
+ new HashMap<FlightEvent.Type, Color>();
+ static {
+ EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255, 0, 0));
+ EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0, 80, 196));
+ EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0, 100, 80));
+ EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230, 130, 15));
+ EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80, 55, 40));
+ EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80, 55, 40));
+ EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80, 55, 40));
+ EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15, 120, 15));
+ EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0, 0, 128));
+ EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0, 0, 0));
+ EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
+ }
+
+ private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
+ new HashMap<FlightEvent.Type, Image>();
+ static {
+ loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
+ loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
+ loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
+ loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
+ loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
+ loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
+ loadImage(FlightEvent.Type.STAGE_SEPARATION,
+ "pix/eventicons/event-stage-separation.png");
+ loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
+ loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
+ "pix/eventicons/event-recovery-device-deployment.png");
+ loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
+ loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
+ }
+
+ private static void loadImage(FlightEvent.Type type, String file) {
+ InputStream is;
+
+ is = ClassLoader.getSystemResourceAsStream(file);
+ if (is == null) {
+ System.out.println("ERROR: File " + file + " not found!");
+ return;
+ }
+
+ try {
+ Image image = ImageIO.read(is);
+ EVENT_IMAGES.put(type, image);
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ }
+
+
+
+
+ private final List<ModifiedXYItemRenderer> renderers =
+ new ArrayList<ModifiedXYItemRenderer>();
+
+ private SimulationPlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
+ //// Flight data plot
+ super(parent, trans.get("PlotDialog.title.Flightdataplot"));
+ this.setModalityType(ModalityType.DOCUMENT_MODAL);
+
+ final boolean initialShowPoints = Prefs.getBoolean(Prefs.PLOT_SHOW_POINTS, false);
+
+
+ // Fill the auto-selections
+ FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
+ PlotConfiguration filled = config.fillAutoAxes(branch);
+ List<Axis> axes = filled.getAllAxes();
+
+
+ // Create the data series for both axes
+ XYSeriesCollection[] data = new XYSeriesCollection[2];
+ data[0] = new XYSeriesCollection();
+ data[1] = new XYSeriesCollection();
+
+
+ // Get the domain axis type
+ final FlightDataType domainType = filled.getDomainAxisType();
+ final Unit domainUnit = filled.getDomainAxisUnit();
+ if (domainType == null) {
+ throw new IllegalArgumentException("Domain axis type not specified.");
+ }
+ List<Double> x = branch.get(domainType);
+
+
+ // Get plot length (ignore trailing NaN's)
+ int typeCount = filled.getTypeCount();
+ int dataLength = 0;
+ for (int i = 0; i < typeCount; i++) {
+ FlightDataType type = filled.getType(i);
+ List<Double> y = branch.get(type);
+
+ for (int j = dataLength; j < y.size(); j++) {
+ if (!Double.isNaN(y.get(j)) && !Double.isInfinite(y.get(j)))
+ dataLength = j;
+ }
+ }
+ dataLength = Math.min(dataLength, x.size());
+
+
+ // Create the XYSeries objects from the flight data and store into the collections
+ String[] axisLabel = new String[2];
+ for (int i = 0; i < typeCount; i++) {
+ // Get info
+ FlightDataType type = filled.getType(i);
+ Unit unit = filled.getUnit(i);
+ int axis = filled.getAxis(i);
+ String name = getLabel(type, unit);
+
+ // Store data in provided units
+ List<Double> y = branch.get(type);
+ XYSeries series = new XYSeries(name, false, true);
+ for (int j = 0; j < dataLength; j++) {
+ series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
+ }
+ data[axis].addSeries(series);
+
+ // Update axis label
+ if (axisLabel[axis] == null)
+ axisLabel[axis] = type.getName();
+ else
+ axisLabel[axis] += "; " + type.getName();
+ }
+
+
+ // Create the chart using the factory to get all default settings
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ //// Simulated flight
+ trans.get("PlotDialog.Chart.Simulatedflight"),
+ null,
+ null,
+ null,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false
+ );
+
+ chart.addSubtitle(new TextTitle(config.getName()));
+
+ // Add the data and formatting to the plot
+ XYPlot plot = chart.getXYPlot();
+ int axisno = 0;
+ for (int i = 0; i < 2; i++) {
+ // Check whether axis has any data
+ if (data[i].getSeriesCount() > 0) {
+ // Create and set axis
+ double min = axes.get(i).getMinValue();
+ double max = axes.get(i).getMaxValue();
+ NumberAxis axis = new PresetNumberAxis(min, max);
+ axis.setLabel(axisLabel[i]);
+ // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
+ plot.setRangeAxis(axisno, axis);
+
+ // Add data and map to the axis
+ plot.setDataset(axisno, data[i]);
+ ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
+ r.setBaseShapesVisible(initialShowPoints);
+ r.setBaseShapesFilled(true);
+ for (int j = 0; j < data[i].getSeriesCount(); j++) {
+ r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
+ }
+ renderers.add(r);
+ plot.setRenderer(axisno, r);
+ plot.mapDatasetToRangeAxis(axisno, axisno);
+ axisno++;
+ }
+ }
+
+ plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
+ plot.addDomainMarker(new ValueMarker(0));
+ plot.addRangeMarker(new ValueMarker(0));
+
+
+
+ // Create list of events to show (combine event too close to each other)
+ ArrayList<Double> timeList = new ArrayList<Double>();
+ ArrayList<String> eventList = new ArrayList<String>();
+ ArrayList<Color> colorList = new ArrayList<Color>();
+ ArrayList<Image> imageList = new ArrayList<Image>();
+
+ HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
+
+ double prevTime = -100;
+ String text = null;
+ Color color = null;
+ Image image = null;
+
+ List<FlightEvent> events = branch.getEvents();
+ for (int i = 0; i < events.size(); i++) {
+ FlightEvent event = events.get(i);
+ double t = event.getTime();
+ FlightEvent.Type type = event.getType();
+
+ if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
+ if (Math.abs(t - prevTime) <= 0.01) {
+
+ if (!typeSet.contains(type)) {
+ text = text + ", " + type.toString();
+ color = getEventColor(type);
+ image = EVENT_IMAGES.get(type);
+ typeSet.add(type);
+ }
+
+ } else {
+
+ if (text != null) {
+ timeList.add(prevTime);
+ eventList.add(text);
+ colorList.add(color);
+ imageList.add(image);
+ }
+ prevTime = t;
+ text = type.toString();
+ color = getEventColor(type);
+ image = EVENT_IMAGES.get(type);
+ typeSet.clear();
+ typeSet.add(type);
+
+ }
+ }
+ }
+ if (text != null) {
+ timeList.add(prevTime);
+ eventList.add(text);
+ colorList.add(color);
+ imageList.add(image);
+ }
+
+
+ // Create the event markers
+
+ if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
+
+ // Domain time is plotted as vertical markers
+ for (int i = 0; i < eventList.size(); i++) {
+ double t = timeList.get(i);
+ String event = eventList.get(i);
+ color = colorList.get(i);
+
+ ValueMarker m = new ValueMarker(t);
+ m.setLabel(event);
+ m.setPaint(color);
+ m.setLabelPaint(color);
+ m.setAlpha(0.7f);
+ plot.addDomainMarker(m);
+ }
+
+ } else {
+
+ // Other domains are plotted as image annotations
+ List<Double> time = branch.get(FlightDataType.TYPE_TIME);
+ List<Double> domain = branch.get(config.getDomainAxisType());
+
+ for (int i = 0; i < eventList.size(); i++) {
+ final double t = timeList.get(i);
+ String event = eventList.get(i);
+ image = imageList.get(i);
+
+ if (image == null)
+ continue;
+
+ // Calculate index and interpolation position a
+ final double a;
+ int tindex = Collections.binarySearch(time, t);
+ if (tindex < 0) {
+ tindex = -tindex - 1;
+ }
+ if (tindex >= time.size()) {
+ // index greater than largest value in time list
+ tindex = time.size() - 1;
+ a = 0;
+ } else if (tindex <= 0) {
+ // index smaller than smallest value in time list
+ tindex = 0;
+ a = 0;
+ } else {
+ tindex--;
+ double t1 = time.get(tindex);
+ double t2 = time.get(tindex + 1);
+
+ if ((t1 > t) || (t2 < t)) {
+ throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
+ }
+
+ if (MathUtil.equals(t1, t2)) {
+ a = 0;
+ } else {
+ a = 1 - (t - t1) / (t2 - t1);
+ }
+ }
+
+ double xcoord;
+ if (a == 0) {
+ xcoord = domain.get(tindex);
+ } else {
+ xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
+ }
+
+ for (int index = 0; index < config.getTypeCount(); index++) {
+ FlightDataType type = config.getType(index);
+ List<Double> range = branch.get(type);
+
+ // Image annotations are not supported on the right-side axis
+ // TODO: LOW: Can this be achieved by JFreeChart?
+ if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
+ continue;
+ }
+
+ double ycoord;
+ if (a == 0) {
+ ycoord = range.get(tindex);
+ } else {
+ ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
+ }
+
+ // Convert units
+ xcoord = config.getDomainAxisUnit().toUnit(xcoord);
+ ycoord = config.getUnit(index).toUnit(ycoord);
+
+ XYImageAnnotation annotation =
+ new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
+ annotation.setToolTipText(event);
+ plot.addAnnotation(annotation);
+ }
+ }
+ }
+
+
+ // Create the dialog
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+ this.add(panel);
+
+ ChartPanel chartPanel = new ChartPanel(chart,
+ false, // properties
+ true, // save
+ false, // print
+ true, // zoom
+ true); // tooltips
+ chartPanel.setMouseWheelEnabled(true);
+ chartPanel.setEnforceFileExtensions(true);
+ chartPanel.setInitialDelay(500);
+
+ chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
+
+ panel.add(chartPanel, "grow, wrap 20lp");
+
+ //// Show data points
+ final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
+ check.setSelected(initialShowPoints);
+ check.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ boolean show = check.isSelected();
+ Prefs.putBoolean(Prefs.PLOT_SHOW_POINTS, show);
+ for (ModifiedXYItemRenderer r : renderers) {
+ r.setBaseShapesVisible(show);
+ }
+ }
+ });
+ panel.add(check, "split, left");
+
+
+ JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
+ panel.add(label, "gapleft para");
+
+
+ panel.add(new JPanel(), "growx");
+
+ //// Close button
+ JButton button = new JButton(trans.get("dlg.but.close"));
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SimulationPlotDialog.this.dispose();
+ }
+ });
+ panel.add(button, "right");
+
+ this.setLocationByPlatform(true);
+ this.pack();
+
+ GUIUtil.setDisposableDialogOptions(this, button);
+ }
+
+ private String getLabel(FlightDataType type, Unit unit) {
+ String name = type.getName();
+ if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
+ !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
+ name += " (" + unit.getUnit() + ")";
+ return name;
+ }
+
+
+
+ private class PresetNumberAxis extends NumberAxis {
+ private final double min;
+ private final double max;
+
+ public PresetNumberAxis(double min, double max) {
+ this.min = min;
+ this.max = max;
+ autoAdjustRange();
+ }
+
+ @Override
+ protected void autoAdjustRange() {
+ this.setRange(min, max);
+ }
+ }
+
+
+ /**
+ * Static method that shows a plot with the specified parameters.
+ *
+ * @param parent the parent window, which will be blocked.
+ * @param simulation the simulation to plot.
+ * @param config the configuration of the plot.
+ */
+ public static void showPlot(Window parent, Simulation simulation, PlotConfiguration config) {
+ new SimulationPlotDialog(parent, simulation, config).setVisible(true);
+ }
+
+
+
+ private static Color getEventColor(FlightEvent.Type type) {
+ Color c = EVENT_COLORS.get(type);
+ if (c != null)
+ return c;
+ return DEFAULT_EVENT_COLOR;
+ }
+
+
+
+
+
+ /**
+ * A modification to the standard renderer that renders the domain marker
+ * labels vertically instead of horizontally.
+ */
+ private static class ModifiedXYItemRenderer extends StandardXYItemRenderer {
+
+ @Override
+ public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
+ Marker marker, Rectangle2D dataArea) {
+
+ if (!(marker instanceof ValueMarker)) {
+ // Use parent for all others
+ super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
+ return;
+ }
+
+ /*
+ * Draw the normal marker, but with rotated text.
+ * Copied from the overridden method.
+ */
+ ValueMarker vm = (ValueMarker) marker;
+ double value = vm.getValue();
+ Range range = domainAxis.getRange();
+ if (!range.contains(value)) {
+ return;
+ }
+
+ double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
+
+ PlotOrientation orientation = plot.getOrientation();
+ Line2D line = null;
+ if (orientation == PlotOrientation.HORIZONTAL) {
+ line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
+ } else {
+ line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
+ }
+
+ final Composite originalComposite = g2.getComposite();
+ g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
+ .getAlpha()));
+ g2.setPaint(marker.getPaint());
+ g2.setStroke(marker.getStroke());
+ g2.draw(line);
+
+ String label = marker.getLabel();
+ RectangleAnchor anchor = marker.getLabelAnchor();
+ if (label != null) {
+ Font labelFont = marker.getLabelFont();
+ g2.setFont(labelFont);
+ g2.setPaint(marker.getLabelPaint());
+ Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
+ orientation, dataArea, line.getBounds2D(), marker
+ .getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
+
+ // Changed:
+ TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
+ TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
+ (float) coordinates.getY(), textAnchor,
+ -Math.PI / 2, textAnchor);
+ }
+ g2.setComposite(originalComposite);
+ }
+
+ }
+
+}
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.Icons;
+/**
+ * Panel that displays the simulation plot options to the user.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class SimulationPlotPanel extends JPanel {
private static final Translator trans = Application.getTranslator();
-
+
// TODO: LOW: Should these be somewhere else?
public static final int AUTO = -1;
public static final int LEFT = 0;
JOptionPane.showMessageDialog(SimulationPlotPanel.this,
//// A maximum of 15 plots is allowed.
//// Cannot add plot
- trans.get("simplotpanel.OptionPane.lbl1"),
+ trans.get("simplotpanel.OptionPane.lbl1"),
trans.get("simplotpanel.OptionPane.lbl2"),
JOptionPane.ERROR_MESSAGE);
return;
@Override
public void actionPerformed(ActionEvent e) {
defaultConfiguration = configuration.clone();
- PlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
+ SimulationPlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
simulation, configuration);
}
});
public void itemStateChanged(ItemEvent e) {
if (modifying > 0)
return;
- Unit unit = (Unit) unitSelector.getSelectedUnit();
+ Unit unit = unitSelector.getSelectedUnit();
configuration.setPlotDataUnit(index, unit);
}
});
FlightData flight = null;
try {
Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
- simulation.getConditions().setMotorConfigurationID(motorId);
+ simulation.getOptions().setMotorConfigurationID(motorId);
simulation.simulate();
flight = simulation.getSimulatedData();
} catch (SimulationException e1) {
import java.util.ArrayList;
import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Transformation;
public class SymmetricComponentShapes extends RocketComponentShapes {
private static final int MINPOINTS = 91;
- private static final double ACCEPTABLE_ANGLE = Math.cos(7.0*Math.PI/180.0);
+ private static final double ACCEPTABLE_ANGLE = Math.cos(7.0 * Math.PI / 180.0);
// TODO: HIGH: adaptiveness sucks, remove it.
// TODO: LOW: Uses only first component of cluster (not currently clusterable)
- public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
+ public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
Transformation transformation) {
- net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent)component;
+ net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent) component;
int i;
final double delta = 0.0000001;
ArrayList<Coordinate> points = new ArrayList<Coordinate>();
x = delta;
- points.add(new Coordinate(x,c.getRadius(x),0));
- for (i=1; i < MINPOINTS-1; i++) {
- x = c.getLength()*i/(MINPOINTS-1);
- points.add(new Coordinate(x,c.getRadius(x),0));
+ points.add(new Coordinate(x, c.getRadius(x), 0));
+ for (i = 1; i < MINPOINTS - 1; i++) {
+ x = c.getLength() * i / (MINPOINTS - 1);
+ points.add(new Coordinate(x, c.getRadius(x), 0));
//System.out.println("Starting with x="+x);
}
x = c.getLength() - delta;
- points.add(new Coordinate(x,c.getRadius(x),0));
+ points.add(new Coordinate(x, c.getRadius(x), 0));
-
- i=0;
- while (i < points.size()-2) {
- if (angleAcceptable(points.get(i),points.get(i+1),points.get(i+2)) ||
- points.get(i+1).x - points.get(i).x < 0.001) { // 1mm
+
+ i = 0;
+ while (i < points.size() - 2) {
+ if (angleAcceptable(points.get(i), points.get(i + 1), points.get(i + 2)) ||
+ points.get(i + 1).x - points.get(i).x < 0.001) { // 1mm
i++;
continue;
}
-
+
// Split the longer of the areas
int n;
- if (points.get(i+2).x-points.get(i+1).x > points.get(i+1).x-points.get(i).x)
- n = i+1;
+ if (points.get(i + 2).x - points.get(i + 1).x > points.get(i + 1).x - points.get(i).x)
+ n = i + 1;
else
n = i;
- x = (points.get(n).x + points.get(n+1).x)/2;
- points.add(n+1,new Coordinate(x,c.getRadius(x),0));
+ x = (points.get(n).x + points.get(n + 1).x) / 2;
+ points.add(n + 1, new Coordinate(x, c.getRadius(x), 0));
}
//System.out.println("Final points: "+points.size());
final int len = points.size();
-
- for (i=0; i < len; i++) {
+
+ for (i = 0; i < len; i++) {
points.set(i, c.toAbsolute(points.get(i))[0]);
}
-
+
/* Show points:
Shape[] s = new Shape[len+1];
final double d=0.001;
s[i] = new Ellipse2D.Double(points.get(i).x()-d/2,points.get(i).y()-d/2,d,d);
}
*/
-
+
//System.out.println("here");
// TODO: LOW: curved path instead of linear
Path2D.Double path = new Path2D.Double();
- path.moveTo(points.get(len-1).x*S, points.get(len-1).y*S);
- for (i=len-2; i>=0; i--) {
- path.lineTo(points.get(i).x*S, points.get(i).y*S);
+ path.moveTo(points.get(len - 1).x * S, points.get(len - 1).y * S);
+ for (i = len - 2; i >= 0; i--) {
+ path.lineTo(points.get(i).x * S, points.get(i).y * S);
}
- for (i=0; i<len; i++) {
- path.lineTo(points.get(i).x*S, -points.get(i).y*S);
+ for (i = 0; i < len; i++) {
+ path.lineTo(points.get(i).x * S, -points.get(i).y * S);
}
- path.lineTo(points.get(len-1).x*S, points.get(len-1).y*S);
+ path.lineTo(points.get(len - 1).x * S, points.get(len - 1).y * S);
path.closePath();
//s[len] = path;
//return s;
- return new Shape[]{ path };
+ return new Shape[] { path };
}
-
+
private static boolean angleAcceptable(Coordinate v1, Coordinate v2, Coordinate v3) {
- return (cosAngle(v1,v2,v3) > ACCEPTABLE_ANGLE);
+ return (cosAngle(v1, v2, v3) > ACCEPTABLE_ANGLE);
}
+
/*
* cosAngle = v1.v2 / |v1|*|v2| = v1.v2 / sqrt(v1.v1*v2.v2)
*/
double cos;
double len;
cos = Coordinate.dot(v1.sub(v2), v2.sub(v3));
- len = Math.sqrt(v1.sub(v2).length2() * v2.sub(v3).length2());
- return cos/len;
+ len = MathUtil.safeSqrt(v1.sub(v2).length2() * v2.sub(v3).length2());
+ return cos / len;
}
}
public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure {
-
- // Number of pixels to leave at edges when fitting figure
- public static final int BORDER_PIXELS_WIDTH=30;
- public static final int BORDER_PIXELS_HEIGHT=20;
+ // Number of pixels to leave at edges when fitting figure
+ private static final int DEFAULT_BORDER_PIXELS_WIDTH = 30;
+ private static final int DEFAULT_BORDER_PIXELS_HEIGHT = 20;
- protected final double dpi;
+ protected final double dpi;
+
protected double scale = 1.0;
protected double scaling = 1.0;
+ protected int borderPixelsWidth = DEFAULT_BORDER_PIXELS_WIDTH;
+ protected int borderPixelsHeight = DEFAULT_BORDER_PIXELS_HEIGHT;
+
protected final List<ChangeListener> listeners = new LinkedList<ChangeListener>();
-
+
public AbstractScaleFigure() {
this.dpi = Prefs.getDPI();
this.scaling = 1.0;
- this.scale = dpi/0.0254*scaling;
+ this.scale = dpi / 0.0254 * scaling;
setBackground(Color.WHITE);
setOpaque(true);
}
-
+
public abstract void updateFigure();
+
public abstract double getFigureWidth();
+
public abstract double getFigureHeight();
-
+
@Override
public double getScaling() {
return scaling;
}
-
+
@Override
public double getAbsoluteScale() {
return scale;
if (Math.abs(this.scaling - scaling) < 0.01)
return;
this.scaling = scaling;
- this.scale = dpi/0.0254*scaling;
+ this.scale = dpi / 0.0254 * scaling;
updateFigure();
}
@Override
public void setScaling(Dimension bounds) {
double zh = 1, zv = 1;
- int w = bounds.width - 2*BORDER_PIXELS_WIDTH -20;
- int h = bounds.height - 2*BORDER_PIXELS_HEIGHT -20;
+ int w = bounds.width - 2 * borderPixelsWidth - 20;
+ int h = bounds.height - 2 * borderPixelsHeight - 20;
if (w < 10)
w = 10;
if (h < 10)
h = 10;
- zh = ((double)w) / getFigureWidth();
- zv = ((double)h) / getFigureHeight();
-
- double s = Math.min(zh, zv)/dpi*0.0254 - 0.001;
+ zh = (w) / getFigureWidth();
+ zv = (h) / getFigureHeight();
+
+ double s = Math.min(zh, zv) / dpi * 0.0254 - 0.001;
setScaling(s);
}
-
-
+
+ @Override
+ public Dimension getBorderPixels() {
+ return new Dimension(borderPixelsWidth, borderPixelsHeight);
+ }
+
+ @Override
+ public void setBorderPixels(int width, int height) {
+ this.borderPixelsWidth = width;
+ this.borderPixelsHeight = height;
+ }
+
+
@Override
public void addChangeListener(ChangeListener listener) {
- listeners.add(0,listener);
+ listeners.add(0, listener);
}
-
+
@Override
public void removeChangeListener(ChangeListener listener) {
listeners.remove(listener);
}
private ChangeEvent changeEvent = null;
+
protected void fireChangeEvent() {
ChangeListener[] list = listeners.toArray(new ChangeListener[0]);
- for (ChangeListener l: list) {
+ for (ChangeListener l : list) {
if (changeEvent == null)
changeEvent = new ChangeEvent(this);
l.stateChanged(changeEvent);
}
}
-
+
}
// TODO: MEDIUM: the figure jumps and bugs when using automatic fitting
public class FinPointFigure extends AbstractScaleFigure {
-
+
private static final int BOX_SIZE = 4;
private final FreeformFinSet finset;
this.finset = finset;
}
-
+
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
-
+
if (modID != finset.getRocket().getAerodynamicModID()) {
modID = finset.getRocket().getAerodynamicModID();
calculateDimensions();
}
-
+
double tx, ty;
// Calculate translation for figure centering
- if (figureWidth*scale + 2*BORDER_PIXELS_WIDTH < getWidth()) {
-
+ if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) {
+
// Figure fits in the viewport
- tx = (getWidth()-figureWidth*scale)/2 - minX*scale;
-
+ tx = (getWidth() - figureWidth * scale) / 2 - minX * scale;
+
} else {
-
+
// Figure does not fit in viewport
- tx = BORDER_PIXELS_WIDTH - minX*scale;
+ tx = borderPixelsWidth - minX * scale;
}
- if (figureHeight*scale + 2*BORDER_PIXELS_HEIGHT < getHeight()) {
- ty = getHeight() - BORDER_PIXELS_HEIGHT;
+ if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) {
+ ty = getHeight() - borderPixelsHeight;
} else {
- ty = BORDER_PIXELS_HEIGHT + figureHeight*scale;
+ ty = borderPixelsHeight + figureHeight * scale;
}
- if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+ if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
// Origin has changed, fire event
translateX = tx;
translateY = ty;
fireChangeEvent();
}
-
- if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+
+ if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
// Origin has changed, fire event
translateX = tx;
translateY = ty;
// Calculate and store the transformation used
transform = new AffineTransform();
transform.translate(translateX, translateY);
- transform.scale(scale/EXTRA_SCALE, -scale/EXTRA_SCALE);
+ transform.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
// TODO: HIGH: border Y-scale upwards
-
+
g2.transform(transform);
// Set rendering hints appropriately
- g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
- g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
+
-
Rectangle visible = g2.getClipBounds();
- double x0 = ((double)visible.x-3)/EXTRA_SCALE;
- double x1 = ((double)visible.x+visible.width+4)/EXTRA_SCALE;
- double y0 = ((double)visible.y-3)/EXTRA_SCALE;
- double y1 = ((double)visible.y+visible.height+4)/EXTRA_SCALE;
-
+ double x0 = ((double) visible.x - 3) / EXTRA_SCALE;
+ double x1 = ((double) visible.x + visible.width + 4) / EXTRA_SCALE;
+ double y0 = ((double) visible.y - 3) / EXTRA_SCALE;
+ double y1 = ((double) visible.y + visible.height + 4) / EXTRA_SCALE;
+
// Background grid
- g2.setStroke(new BasicStroke((float)(1.0*EXTRA_SCALE/scale),
+ g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(new Color(0,0,255,30));
-
+ g2.setColor(new Color(0, 0, 255, 30));
+
Unit unit;
if (this.getParent() != null &&
this.getParent().getParent() instanceof ScaleScrollPane) {
- unit = ((ScaleScrollPane)this.getParent().getParent()).getCurrentUnit();
+ unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
} else {
unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
}
// vertical
- Tick[] ticks = unit.getTicks(x0, x1,
- ScaleScrollPane.MINOR_TICKS/scale,
- ScaleScrollPane.MAJOR_TICKS/scale);
- Line2D.Double line = new Line2D.Double();
- for (Tick t: ticks) {
- if (t.major) {
- line.setLine(t.value*EXTRA_SCALE, y0*EXTRA_SCALE,
- t.value*EXTRA_SCALE, y1*EXTRA_SCALE);
- g2.draw(line);
- }
- }
-
- // horizontal
- ticks = unit.getTicks(y0, y1,
- ScaleScrollPane.MINOR_TICKS/scale,
- ScaleScrollPane.MAJOR_TICKS/scale);
- for (Tick t: ticks) {
- if (t.major) {
- line.setLine(x0*EXTRA_SCALE, t.value*EXTRA_SCALE,
- x1*EXTRA_SCALE, t.value*EXTRA_SCALE);
- g2.draw(line);
- }
- }
-
-
-
-
+ Tick[] ticks = unit.getTicks(x0, x1,
+ ScaleScrollPane.MINOR_TICKS / scale,
+ ScaleScrollPane.MAJOR_TICKS / scale);
+ Line2D.Double line = new Line2D.Double();
+ for (Tick t : ticks) {
+ if (t.major) {
+ line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE,
+ t.value * EXTRA_SCALE, y1 * EXTRA_SCALE);
+ g2.draw(line);
+ }
+ }
+
+ // horizontal
+ ticks = unit.getTicks(y0, y1,
+ ScaleScrollPane.MINOR_TICKS / scale,
+ ScaleScrollPane.MAJOR_TICKS / scale);
+ for (Tick t : ticks) {
+ if (t.major) {
+ line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE,
+ x1 * EXTRA_SCALE, t.value * EXTRA_SCALE);
+ g2.draw(line);
+ }
+ }
+
+
+
+
// Base rocket line
- g2.setStroke(new BasicStroke((float)(3.0*EXTRA_SCALE/scale),
- BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
+ g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g2.setColor(Color.GRAY);
- g2.drawLine((int)(x0*EXTRA_SCALE), 0, (int)(x1*EXTRA_SCALE), 0);
-
+ g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0);
+
// Fin shape
Coordinate[] points = finset.getFinPoints();
Path2D.Double shape = new Path2D.Double();
shape.moveTo(0, 0);
- for (int i=1; i < points.length; i++) {
- shape.lineTo(points[i].x*EXTRA_SCALE, points[i].y*EXTRA_SCALE);
+ for (int i = 1; i < points.length; i++) {
+ shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE);
}
- g2.setStroke(new BasicStroke((float)(1.0*EXTRA_SCALE/scale),
- BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
+ g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g2.setColor(Color.BLACK);
g2.draw(shape);
-
+
// Fin point boxes
- g2.setColor(new Color(150,0,0));
- double s = BOX_SIZE*EXTRA_SCALE/scale;
+ g2.setColor(new Color(150, 0, 0));
+ double s = BOX_SIZE * EXTRA_SCALE / scale;
handles = new Rectangle2D.Double[points.length];
- for (int i=0; i < points.length; i++) {
+ for (int i = 0; i < points.length; i++) {
Coordinate c = points[i];
- handles[i] = new Rectangle2D.Double(c.x*EXTRA_SCALE-s, c.y*EXTRA_SCALE-s, 2*s, 2*s);
+ handles[i] = new Rectangle2D.Double(c.x * EXTRA_SCALE - s, c.y * EXTRA_SCALE - s, 2 * s, 2 * s);
g2.draw(handles[i]);
}
}
-
+
public int getIndexByPoint(double x, double y) {
if (handles == null)
return -1;
// Calculate point in shapes' coordinates
- Point2D.Double p = new Point2D.Double(x,y);
+ Point2D.Double p = new Point2D.Double(x, y);
try {
- transform.inverseTransform(p,p);
+ transform.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
return -1;
}
-
- for (int i=0; i < handles.length; i++) {
+
+ for (int i = 0; i < handles.length; i++) {
if (handles[i].contains(p))
return i;
}
return -1;
// Calculate point in shapes' coordinates
- Point2D.Double p = new Point2D.Double(x,y);
+ Point2D.Double p = new Point2D.Double(x, y);
try {
- transform.inverseTransform(p,p);
+ transform.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
return -1;
}
double x0 = p.x / EXTRA_SCALE;
double y0 = p.y / EXTRA_SCALE;
double delta = BOX_SIZE / scale;
-
- System.out.println("Point: "+x0+","+y0);
- System.out.println("delta: "+(BOX_SIZE/scale));
+
+ System.out.println("Point: " + x0 + "," + y0);
+ System.out.println("delta: " + (BOX_SIZE / scale));
Coordinate[] points = finset.getFinPoints();
- for (int i=1; i < points.length; i++) {
- double x1 = points[i-1].x;
- double y1 = points[i-1].y;
+ for (int i = 1; i < points.length; i++) {
+ double x1 = points[i - 1].x;
+ double y1 = points[i - 1].y;
double x2 = points[i].x;
double y2 = points[i].y;
-// System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2);
+ // System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2);
- double u = Math.abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) /
- MathUtil.hypot(x2-x1, y2-y1);
- System.out.println("Distance of segment "+i+" is "+u);
+ double u = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) /
+ MathUtil.hypot(x2 - x1, y2 - y1);
+ System.out.println("Distance of segment " + i + " is " + u);
if (u < delta)
return i;
}
public Point2D.Double convertPoint(double x, double y) {
- Point2D.Double p = new Point2D.Double(x,y);
+ Point2D.Double p = new Point2D.Double(x, y);
try {
- transform.inverseTransform(p,p);
+ transform.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
- assert(false): "Should not occur";
- return new Point2D.Double(0,0);
+ assert (false) : "Should not occur";
+ return new Point2D.Double(0, 0);
}
-
+
p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE);
return p;
}
modID = finset.getRocket().getAerodynamicModID();
calculateDimensions();
}
- return new Dimension((int)translateX, (int)translateY);
+ return new Dimension((int) translateX, (int) translateY);
}
-
+
@Override
public double getFigureWidth() {
if (modID != finset.getRocket().getAerodynamicModID()) {
}
return figureWidth;
}
-
+
@Override
public double getFigureHeight() {
if (modID != finset.getRocket().getAerodynamicModID()) {
}
return figureHeight;
}
-
+
private void calculateDimensions() {
minX = 0;
maxX = 0;
maxY = 0;
- for (Coordinate c: finset.getFinPoints()) {
+ for (Coordinate c : finset.getFinPoints()) {
if (c.x < minX)
minX = c.x;
if (c.x > maxX)
figureHeight = maxY;
- Dimension d = new Dimension((int)(figureWidth*scale+2*BORDER_PIXELS_WIDTH),
- (int)(figureHeight*scale+2*BORDER_PIXELS_HEIGHT));
+ Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth),
+ (int) (figureHeight * scale + 2 * borderPixelsHeight));
if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
setPreferredSize(d);
repaint();
}
-
+
}
package net.sf.openrocket.gui.scalefigure;
-import net.sf.openrocket.gui.figureelements.FigureElement;
-import net.sf.openrocket.gui.main.ExceptionHandler;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.LineStyle;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Prefs;
-import net.sf.openrocket.util.Reflection;
-import net.sf.openrocket.util.Transformation;
-
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.util.Iterator;
import java.util.LinkedHashSet;
+import net.sf.openrocket.gui.figureelements.FigureElement;
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.LineStyle;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.Reflection;
+import net.sf.openrocket.util.Transformation;
+
/**
* A <code>ScaleFigure</code> that draws a complete rocket. Extra information can
* be added to the figure by the methods {@link #addRelativeExtra(FigureElement)},
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-
public class RocketFigure extends AbstractScaleFigure {
private static final long serialVersionUID = 1L;
// Width for drawing normal and selected components
public static final double NORMAL_WIDTH = 1.0;
public static final double SELECTED_WIDTH = 2.0;
-
- private final Configuration configuration;
+
+ private Configuration configuration;
private RocketComponent[] selection = new RocketComponent[0];
private int type = TYPE_SIDE;
-
+
private double rotation;
private Transformation transformation;
private double translateX, translateY;
-
-
+
+
/*
* figureComponents contains the corresponding RocketComponents of the figureShapes
*/
private final ArrayList<Shape> figureShapes = new ArrayList<Shape>();
- private final ArrayList<RocketComponent> figureComponents =
- new ArrayList<RocketComponent>();
+ private final ArrayList<RocketComponent> figureComponents =
+ new ArrayList<RocketComponent>();
- private double minX=0, maxX=0, maxR=0;
+ private double minX = 0, maxX = 0, maxR = 0;
// Figure width and height in SI-units and pixels
- private double figureWidth=0, figureHeight=0;
- private int figureWidthPx=0, figureHeightPx=0;
+ private double figureWidth = 0, figureHeight = 0;
+ private int figureWidthPx = 0, figureHeightPx = 0;
private AffineTransform g2transformation = null;
this.rotation = 0.0;
this.transformation = Transformation.rotate_x(0.0);
- calculateSize();
updateFigure();
}
+ /**
+ * Set the configuration displayed by the figure. It may use the same or different rocket.
+ *
+ * @param configuration the configuration to display.
+ */
+ public void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ updateFigure();
+ }
+
+ @Override
public Dimension getOrigin() {
- return new Dimension((int)translateX, (int)translateY);
+ return new Dimension((int) translateX, (int) translateY);
}
@Override
public double getFigureHeight() {
return figureHeight;
}
-
+
@Override
public double getFigureWidth() {
return figureWidth;
}
-
+
public RocketComponent[] getSelection() {
return selection;
public void setType(int type) {
if (type != TYPE_BACK && type != TYPE_SIDE) {
- throw new IllegalArgumentException("Illegal type: "+type);
+ throw new IllegalArgumentException("Illegal type: " + type);
}
if (this.type == type)
return;
this.type = type;
updateFigure();
}
-
-
+
/**
* Updates the figure shapes and figure size.
*/
figureComponents.clear();
calculateSize();
-
+
// Get shapes for all active components
- for (RocketComponent c: configuration) {
+ for (RocketComponent c : configuration) {
Shape[] s = getShapes(c);
- for (int i=0; i < s.length; i++) {
+ for (int i = 0; i < s.length; i++) {
figureShapes.add(s[i]);
figureComponents.add(c);
}
repaint();
fireChangeEvent();
}
-
+
public void addRelativeExtra(FigureElement p) {
relativeExtra.add(p);
absoluteExtra.clear();
}
-
+
/**
* Paints the rocket on to the Graphics element.
* <p>
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
- Graphics2D g2 = (Graphics2D)g;
-
+ Graphics2D g2 = (Graphics2D) g;
+
AffineTransform baseTransform = g2.getTransform();
// Update figure shapes if necessary
if (figureShapes == null)
updateFigure();
-
+
double tx, ty;
// Calculate translation for figure centering
- if (figureWidthPx + 2*BORDER_PIXELS_WIDTH < getWidth()) {
-
+ if (figureWidthPx + 2 * borderPixelsWidth < getWidth()) {
+
// Figure fits in the viewport
if (type == TYPE_BACK)
- tx = getWidth()/2;
- else
- tx = (getWidth()-figureWidthPx)/2 - minX*scale;
-
+ tx = getWidth() / 2;
+ else
+ tx = (getWidth() - figureWidthPx) / 2 - minX * scale;
+
} else {
-
+
// Figure does not fit in viewport
if (type == TYPE_BACK)
- tx = BORDER_PIXELS_WIDTH + figureWidthPx/2;
- else
- tx = BORDER_PIXELS_WIDTH - minX*scale;
+ tx = borderPixelsWidth + figureWidthPx / 2;
+ else
+ tx = borderPixelsWidth - minX * scale;
}
-
- ty = computeTy(figureHeightPx);
-
- if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+
+ ty = computeTy(figureHeightPx);
+
+ if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
// Origin has changed, fire event
translateX = tx;
translateY = ty;
g2transformation = new AffineTransform();
g2transformation.translate(translateX, translateY);
// Mirror position Y-axis upwards
- g2transformation.scale(scale/EXTRA_SCALE, -scale/EXTRA_SCALE);
-
+ g2transformation.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
+
g2.transform(g2transformation);
-
+
// Set rendering hints appropriately
- g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
- g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
-
+
// Draw all shapes
- for (int i=0; i < figureShapes.size(); i++) {
+ for (int i = 0; i < figureShapes.size(); i++) {
RocketComponent c = figureComponents.get(i);
Shape s = figureShapes.get(i);
boolean selected = false;
// Check if component is in the selection
- for (int j=0; j < selection.length; j++) {
+ for (int j = 0; j < selection.length; j++) {
if (c == selection[j]) {
selected = true;
break;
color = Prefs.getDefaultColor(c.getClass());
}
g2.setColor(color);
-
+
LineStyle style = c.getLineStyle();
if (style == null)
style = Prefs.getDefaultLineStyle(c.getClass());
float[] dashes = style.getDashes();
- for (int j=0; j<dashes.length; j++) {
+ for (int j = 0; j < dashes.length; j++) {
dashes[j] *= EXTRA_SCALE / scale;
}
if (selected) {
- g2.setStroke(new BasicStroke((float)(SELECTED_WIDTH*EXTRA_SCALE/scale),
- BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL, 0, dashes, 0));
+ g2.setStroke(new BasicStroke((float) (SELECTED_WIDTH * EXTRA_SCALE / scale),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dashes, 0));
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
} else {
- g2.setStroke(new BasicStroke((float)(NORMAL_WIDTH*EXTRA_SCALE/scale),
- BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL, 0, dashes, 0));
- g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH * EXTRA_SCALE / scale),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dashes, 0));
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
}
g2.draw(s);
}
- g2.setStroke(new BasicStroke((float)(NORMAL_WIDTH*EXTRA_SCALE/scale),
- BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
- g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH * EXTRA_SCALE / scale),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
-
+
// Draw motors
String motorID = configuration.getMotorConfigurationID();
Color fillColor = Prefs.getMotorFillColor();
double length = motor.getLength();
double radius = motor.getDiameter() / 2;
- Coordinate[] position = ((RocketComponent)mount).toAbsolute(
- new Coordinate(((RocketComponent)mount).getLength() +
+ Coordinate[] position = ((RocketComponent) mount).toAbsolute(
+ new Coordinate(((RocketComponent) mount).getLength() +
mount.getMotorOverhang() - length));
- for (int i=0; i < position.length; i++) {
+ for (int i = 0; i < position.length; i++) {
position[i] = transformation.transform(position[i]);
}
- for (Coordinate coord: position) {
+ for (Coordinate coord : position) {
Shape s;
if (type == TYPE_SIDE) {
- s = new Rectangle2D.Double(EXTRA_SCALE*coord.x,
- EXTRA_SCALE*(coord.y - radius), EXTRA_SCALE*length,
- EXTRA_SCALE*2*radius);
+ s = new Rectangle2D.Double(EXTRA_SCALE * coord.x,
+ EXTRA_SCALE * (coord.y - radius), EXTRA_SCALE * length,
+ EXTRA_SCALE * 2 * radius);
} else {
- s = new Ellipse2D.Double(EXTRA_SCALE*(coord.z-radius),
- EXTRA_SCALE*(coord.y-radius), EXTRA_SCALE*2*radius,
- EXTRA_SCALE*2*radius);
+ s = new Ellipse2D.Double(EXTRA_SCALE * (coord.z - radius),
+ EXTRA_SCALE * (coord.y - radius), EXTRA_SCALE * 2 * radius,
+ EXTRA_SCALE * 2 * radius);
}
g2.setColor(fillColor);
g2.fill(s);
}
}
-
-
+
+
// Draw relative extras
- for (FigureElement e: relativeExtra) {
- e.paint(g2, scale/EXTRA_SCALE);
+ for (FigureElement e : relativeExtra) {
+ e.paint(g2, scale / EXTRA_SCALE);
}
-
+
// Draw absolute extras
g2.setTransform(baseTransform);
Rectangle rect = this.getVisibleRect();
- for (FigureElement e: absoluteExtra) {
+ for (FigureElement e : absoluteExtra) {
e.paint(g2, 1.0, rect);
}
-
+
}
-
- protected double computeTy (int heightPx) {
- final double ty;
- if (heightPx + 2*BORDER_PIXELS_HEIGHT < getHeight()) {
- ty = getHeight()/2;
- } else {
- ty = BORDER_PIXELS_HEIGHT + heightPx/2;
- }
- return ty;
- }
-
-
- public RocketComponent[] getComponentsByPoint(double x, double y) {
+
+ protected double computeTy(int heightPx) {
+ final double ty;
+ if (heightPx + 2 * borderPixelsHeight < getHeight()) {
+ ty = getHeight() / 2;
+ } else {
+ ty = borderPixelsHeight + heightPx / 2;
+ }
+ return ty;
+ }
+
+
+ public RocketComponent[] getComponentsByPoint(double x, double y) {
// Calculate point in shapes' coordinates
- Point2D.Double p = new Point2D.Double(x,y);
+ Point2D.Double p = new Point2D.Double(x, y);
try {
- g2transformation.inverseTransform(p,p);
+ g2transformation.inverseTransform(p, p);
} catch (NoninvertibleTransformException e) {
return new RocketComponent[0];
}
LinkedHashSet<RocketComponent> l = new LinkedHashSet<RocketComponent>();
-
- for (int i=0; i<figureShapes.size(); i++) {
+
+ for (int i = 0; i < figureShapes.size(); i++) {
if (figureShapes.get(i).contains(p))
l.add(figureComponents.get(i));
}
}
-
+
/**
* Gets the shapes required to draw the component.
*
*/
private Shape[] getShapes(RocketComponent component) {
Reflection.Method m;
-
+
// Find the appropriate method
switch (type) {
case TYPE_SIDE:
- m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesSide",
+ m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesSide",
RocketComponent.class, Transformation.class);
break;
-
+
case TYPE_BACK:
- m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesBack",
+ m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesBack",
RocketComponent.class, Transformation.class);
break;
-
+
default:
- throw new BugException("Unknown figure type = "+type);
+ throw new BugException("Unknown figure type = " + type);
}
if (m == null) {
- ExceptionHandler.handleErrorCondition("ERROR: Rocket figure paint method not found for "
+ ExceptionHandler.handleErrorCondition("ERROR: Rocket figure paint method not found for "
+ component);
return new Shape[0];
}
-
- return (Shape[])m.invokeStatic(component,transformation);
+
+ return (Shape[]) m.invokeStatic(component, transformation);
}
-
+
/**
* Gets the bounds of the figure, i.e. the maximum extents in the selected dimensions.
* The bounds are stored in the variables minX, maxX and maxR.
maxR = 0;
return;
}
-
+
minX = Double.MAX_VALUE;
maxX = Double.MIN_VALUE;
maxR = 0;
- for (Coordinate c: bounds) {
+ for (Coordinate c : bounds) {
double x = c.x, r = MathUtil.hypot(c.y, c.z);
if (x < minX)
minX = x;
public double getBestZoom(Rectangle2D bounds) {
- double zh=1, zv=1;
+ double zh = 1, zv = 1;
if (bounds.getWidth() > 0.0001)
- zh = (getWidth()-2*BORDER_PIXELS_WIDTH)/bounds.getWidth();
+ zh = (getWidth() - 2 * borderPixelsWidth) / bounds.getWidth();
if (bounds.getHeight() > 0.0001)
- zv = (getHeight()-2*BORDER_PIXELS_HEIGHT)/bounds.getHeight();
+ zv = (getHeight() - 2 * borderPixelsHeight) / bounds.getHeight();
return Math.min(zh, zv);
}
-
+
+
/**
* Calculates the necessary size of the figure and set the PreferredSize
* property accordingly.
switch (type) {
case TYPE_SIDE:
- figureWidth = maxX-minX;
- figureHeight = 2*maxR;
+ figureWidth = maxX - minX;
+ figureHeight = 2 * maxR;
break;
-
+
case TYPE_BACK:
- figureWidth = 2*maxR;
- figureHeight = 2*maxR;
+ figureWidth = 2 * maxR;
+ figureHeight = 2 * maxR;
break;
-
+
default:
- assert(false): "Should not occur, type="+type;
+ assert (false) : "Should not occur, type=" + type;
figureWidth = 0;
figureHeight = 0;
}
- figureWidthPx = (int)(figureWidth * scale);
- figureHeightPx = (int)(figureHeight * scale);
-
- Dimension d = new Dimension(figureWidthPx+2*BORDER_PIXELS_WIDTH,
- figureHeightPx+2*BORDER_PIXELS_HEIGHT);
+ figureWidthPx = (int) (figureWidth * scale);
+ figureHeightPx = (int) (figureHeight * scale);
+
+ Dimension d = new Dimension(figureWidthPx + 2 * borderPixelsWidth,
+ figureHeightPx + 2 * borderPixelsHeight);
if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
setPreferredSize(d);
public Rectangle2D getDimensions() {
switch (type) {
case TYPE_SIDE:
- return new Rectangle2D.Double(minX,-maxR,maxX-minX,2*maxR);
+ return new Rectangle2D.Double(minX, -maxR, maxX - minX, 2 * maxR);
case TYPE_BACK:
- return new Rectangle2D.Double(-maxR,-maxR,2*maxR,2*maxR);
+ return new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR);
default:
- throw new BugException("Illegal figure type = "+type);
+ throw new BugException("Illegal figure type = " + type);
}
}
-
+
}
Rocket duplicate = (Rocket) configuration.getRocket().copy();
Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
- simulation.getConditions().setMotorConfigurationID(
+ simulation.getOptions().setMotorConfigurationID(
configuration.getMotorConfigurationID());
backgroundSimulationWorker = new BackgroundSimulationWorker(simulation);
*/
public double getScaling();
-
+
/**
* Return the scale of the figure on px/m.
*
*/
public double getAbsoluteScale();
-
+
/**
* Return the pixel coordinates of the figure origin.
*
*/
public Dimension getOrigin();
+
+ /**
+ * Get the amount of blank space left around the figure.
+ *
+ * @return the amount of horizontal and vertical space left on both sides of the figure.
+ */
+ public Dimension getBorderPixels();
+
+ /**
+ * Set the amount of blank space left around the figure.
+ *
+ * @param width the amount of horizontal space left on both sides of the figure.
+ * @param height the amount of vertical space left on both sides of the figure.
+ */
+ public void setBorderPixels(int width, int height);
}
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public class ScaleScrollPane extends JScrollPane
+public class ScaleScrollPane extends JScrollPane
implements MouseListener, MouseMotionListener {
public static final int RULER_SIZE = 20;
public static final int MINOR_TICKS = 3;
public static final int MAJOR_TICKS = 30;
-
+
private JComponent component;
private ScaleFigure figure;
private JViewport viewport;
-
+
private DoubleModel rulerUnit;
private Ruler horizontalRuler;
private Ruler verticalRuler;
private boolean fit = false;
+ /**
+ * Create a scale scroll pane that allows fitting.
+ *
+ * @param component the component to contain (must implement ScaleFigure)
+ */
public ScaleScrollPane(JComponent component) {
this(component, true);
}
+ /**
+ * Create a scale scroll pane.
+ *
+ * @param component the component to contain (must implement ScaleFigure)
+ * @param allowFit whether automatic fitting of the figure is allowed
+ */
public ScaleScrollPane(JComponent component, boolean allowFit) {
super(component);
-// super(component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
-// JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
if (!(component instanceof ScaleFigure)) {
throw new IllegalArgumentException("component must implement ScaleFigure");
}
this.component = component;
- this.figure = (ScaleFigure)component;
+ this.figure = (ScaleFigure) component;
this.allowFit = allowFit;
- rulerUnit = new DoubleModel(0.0,UnitGroup.UNITS_LENGTH);
+ rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
rulerUnit.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel());
this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel());
this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel());
-
+
this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
-
+
viewport = this.getViewport();
viewport.addMouseListener(this);
viewport.addMouseMotionListener(this);
}
+ /**
+ * Return whether automatic fitting of the figure is allowed.
+ */
public boolean isFittingAllowed() {
return allowFit;
}
+ /**
+ * Return whether the figure is currently automatically fitted within the component bounds.
+ */
public boolean isFitting() {
return fit;
}
+ /**
+ * Set whether the figure is automatically fitted within the component bounds.
+ *
+ * @throws BugException if automatic fitting is disallowed and <code>fit</code> is <code>true</code>
+ */
public void setFitting(boolean fit) {
if (fit && !allowFit) {
throw new BugException("Attempting to fit figure not allowing fit.");
public double getScale() {
return figure.getAbsoluteScale();
}
-
+
public void setScaling(double scale) {
if (fit) {
setFitting(false);
//////////////// Mouse handlers ////////////////
- private int dragStartX=0;
- private int dragStartY=0;
+ private int dragStartX = 0;
+ private int dragStartY = 0;
private Rectangle dragRectangle = null;
-
+
@Override
public void mousePressed(MouseEvent e) {
dragStartX = e.getX();
dragStartY = e.getY();
dragRectangle = viewport.getViewRect();
}
-
+
@Override
public void mouseReleased(MouseEvent e) {
dragRectangle = null;
}
-
+
@Override
public void mouseDragged(MouseEvent e) {
- if (dragRectangle==null) {
+ if (dragRectangle == null) {
return;
}
-
- dragRectangle.setLocation(dragStartX-e.getX(),dragStartY-e.getY());
-
+
+ dragRectangle.setLocation(dragStartX - e.getX(), dragStartY - e.getY());
+
dragStartX = e.getX();
dragStartY = e.getY();
viewport.scrollRectToVisible(dragRectangle);
}
-
+
@Override
public void mouseClicked(MouseEvent e) {
}
-
+
@Override
public void mouseEntered(MouseEvent e) {
}
-
+
@Override
public void mouseExited(MouseEvent e) {
}
-
+
@Override
public void mouseMoved(MouseEvent e) {
}
-
+
//////////////// The view port rulers ////////////////
public void updateSize() {
Dimension d = component.getPreferredSize();
if (orientation == HORIZONTAL) {
- setPreferredSize(new Dimension(d.width+10,RULER_SIZE));
+ setPreferredSize(new Dimension(d.width + 10, RULER_SIZE));
} else {
- setPreferredSize(new Dimension(RULER_SIZE,d.height+10));
+ setPreferredSize(new Dimension(RULER_SIZE, d.height + 10));
}
revalidate();
repaint();
if (orientation == HORIZONTAL) {
px -= origin.width;
} else {
-// px = -(px - origin.height);
+ // px = -(px - origin.height);
px -= origin.height;
}
- return px/figure.getAbsoluteScale();
+ return px / figure.getAbsoluteScale();
}
private int toPx(double l) {
Dimension origin = figure.getOrigin();
- int px = (int)(l * figure.getAbsoluteScale() + 0.5);
+ int px = (int) (l * figure.getAbsoluteScale() + 0.5);
if (orientation == HORIZONTAL) {
px += origin.width;
} else {
px = px + origin.height;
-// px += origin.height;
+ // px += origin.height;
}
return px;
}
- @Override
+ @Override
protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- Graphics2D g2 = (Graphics2D)g;
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D) g;
Rectangle area = g2.getClipBounds();
-
- // Fill area with background color
+
+ // Fill area with background color
g2.setColor(getBackground());
- g2.fillRect(area.x, area.y, area.width, area.height+100);
-
+ g2.fillRect(area.x, area.y, area.width, area.height + 100);
+
- int startpx,endpx;
- if (orientation == HORIZONTAL) {
- startpx = area.x;
- endpx = area.x+area.width;
- } else {
- startpx = area.y;
- endpx = area.y+area.height;
- }
-
- Unit unit = rulerUnit.getCurrentUnit();
- double start,end,minor,major;
- start = fromPx(startpx);
- end = fromPx(endpx);
- minor = MINOR_TICKS/figure.getAbsoluteScale();
- major = MAJOR_TICKS/figure.getAbsoluteScale();
+ int startpx, endpx;
+ if (orientation == HORIZONTAL) {
+ startpx = area.x;
+ endpx = area.x + area.width;
+ } else {
+ startpx = area.y;
+ endpx = area.y + area.height;
+ }
+
+ Unit unit = rulerUnit.getCurrentUnit();
+ double start, end, minor, major;
+ start = fromPx(startpx);
+ end = fromPx(endpx);
+ minor = MINOR_TICKS / figure.getAbsoluteScale();
+ major = MAJOR_TICKS / figure.getAbsoluteScale();
+
+ Tick[] ticks = unit.getTicks(start, end, minor, major);
+
- Tick[] ticks = unit.getTicks(start, end, minor, major);
-
-
- // Set color & hints
- g2.setColor(Color.BLACK);
+ // Set color & hints
+ g2.setColor(Color.BLACK);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
- g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
- for (Tick t: ticks) {
- int position = toPx(t.value);
- drawTick(g2,position,t);
- }
- }
-
- private void drawTick(Graphics g, int position, Tick t) {
- int length;
- String str = null;
- if (t.major) {
- length = RULER_SIZE/2;
- } else {
- if (t.notable)
- length = RULER_SIZE/3;
- else
- length = RULER_SIZE/6;
- }
-
- // Set font
- if (t.major) {
- str = rulerUnit.getCurrentUnit().toString(t.value);
- if (t.notable)
- g.setFont(new Font("SansSerif", Font.BOLD, 9));
- else
- g.setFont(new Font("SansSerif", Font.PLAIN, 9));
- }
-
- // Draw tick & text
- if (orientation == HORIZONTAL) {
- g.drawLine(position, RULER_SIZE-length, position, RULER_SIZE);
- if (str != null)
- g.drawString(str, position, RULER_SIZE-length-1);
- } else {
- g.drawLine(RULER_SIZE-length, position, RULER_SIZE, position);
- if (str != null)
- g.drawString(str, 1, position-1);
- }
- }
+
+ for (Tick t : ticks) {
+ int position = toPx(t.value);
+ drawTick(g2, position, t);
+ }
+ }
+
+ private void drawTick(Graphics g, int position, Tick t) {
+ int length;
+ String str = null;
+ if (t.major) {
+ length = RULER_SIZE / 2;
+ } else {
+ if (t.notable)
+ length = RULER_SIZE / 3;
+ else
+ length = RULER_SIZE / 6;
+ }
+
+ // Set font
+ if (t.major) {
+ str = rulerUnit.getCurrentUnit().toString(t.value);
+ if (t.notable)
+ g.setFont(new Font("SansSerif", Font.BOLD, 9));
+ else
+ g.setFont(new Font("SansSerif", Font.PLAIN, 9));
+ }
+
+ // Draw tick & text
+ if (orientation == HORIZONTAL) {
+ g.drawLine(position, RULER_SIZE - length, position, RULER_SIZE);
+ if (str != null)
+ g.drawString(str, position, RULER_SIZE - length - 1);
+ } else {
+ g.drawLine(RULER_SIZE - length, position, RULER_SIZE, position);
+ if (str != null)
+ g.drawString(str, 1, position - 1);
+ }
+ }
}
}
*/\r
public class PinkNoiseWindModel implements WindModel {\r
\r
- /** Source for seed numbers, may be overridden by get/setSeed(). */\r
- private static final Random seedSource = new Random();\r
+ /** Random value with which to XOR the random seed value */\r
+ private static final int SEED_RANDOMIZATION = 0x7343AA03;\r
\r
+\r
+\r
/** Pink noise alpha parameter. */\r
private static final double ALPHA = 5.0 / 3.0;\r
\r
private double average = 0;\r
private double standardDeviation = 0;\r
\r
- private int seed;\r
+ private final int seed;\r
\r
private PinkNoise randomSource = null;\r
private double time1;\r
\r
\r
/**\r
- * Construct a new wind simulator with a random starting seed value.\r
+ * Construct a new wind simulation with a specific seed value.\r
+ * @param seed the seed value.\r
*/\r
- public PinkNoiseWindModel() {\r
- synchronized (seedSource) {\r
- seed = seedSource.nextInt();\r
- }\r
+ public PinkNoiseWindModel(int seed) {\r
+ this.seed = seed ^ SEED_RANDOMIZATION;\r
}\r
\r
\r
\r
\r
\r
- public int getSeed() {\r
- return seed;\r
- }\r
- \r
- public void setSeed(int seed) {\r
- if (this.seed == seed)\r
- return;\r
- this.seed = seed;\r
- }\r
- \r
- \r
-\r
@Override\r
public Coordinate getWindVelocity(double time, double altitude) {\r
if (time < 0) {\r
*/
public double length() {
if (length < 0) {
- length = Math.sqrt(length2());
+ length = MathUtil.safeSqrt(length2());
}
return length;
}
* This is a parallel pattern search optimization algorithm. The function evaluations are performed
* using an ExecutorService. By default a ThreadPoolExecutor is used that has as many thread defined
* as the system has processors.
+ * <p>
+ * The optimization can be aborted by interrupting the current thread.
*/
public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Statistics {
private static final LogHelper log = Application.getLogger();
private ParallelFunctionCache functionExecutor;
private boolean useExpansion = false;
+ private boolean useCoordinateSearch = false;
private int stepCount = 0;
private int reflectionAcceptance = 0;
List<Point> coordinateSearch = new ArrayList<Point>(simplex.size());
Point current;
double currentValue;
- do {
+ boolean continueOptimization = true;
+ while (continueOptimization) {
log.debug("Starting optimization step with simplex " + simplex +
(simplexComputed ? "" : " (not computed)"));
* Expansion is unlikely as we're mainly dealing with bounded optimization.
*/
createReflection(simplex, reflection);
- createCoordinateSearch(current, step, coordinateSearch);
+ if (useCoordinateSearch)
+ createCoordinateSearch(current, step, coordinateSearch);
if (useExpansion)
createExpansion(simplex, expansion);
functionExecutor.compute(reflection);
- functionExecutor.compute(coordinateSearch);
+ if (useCoordinateSearch)
+ functionExecutor.compute(coordinateSearch);
if (useExpansion)
functionExecutor.compute(expansion);
log.debug("Reflection was successful, aborting coordinate search, " +
(useExpansion ? "computing" : "skipping") + " expansion");
- functionExecutor.abort(coordinateSearch);
+ if (useCoordinateSearch)
+ functionExecutor.abort(coordinateSearch);
simplex.clear();
simplex.add(current);
*/
halveStep(simplex);
functionExecutor.compute(simplex);
- functionExecutor.waitFor(coordinateSearch);
- if (accept(coordinateSearch, currentValue)) {
+ if (useCoordinateSearch) {
+ functionExecutor.waitFor(coordinateSearch);
- log.debug("Coordinate search successful, reseting simplex");
- List<Point> toAbort = new LinkedList<Point>(simplex);
- simplex.clear();
- simplex.add(current);
- for (Point p : pattern) {
- simplex.add(current.add(p.mul(step)));
+ if (accept(coordinateSearch, currentValue)) {
+
+ log.debug("Coordinate search successful, reseting simplex");
+ List<Point> toAbort = new LinkedList<Point>(simplex);
+ simplex.clear();
+ simplex.add(current);
+ for (Point p : pattern) {
+ simplex.add(current.add(p.mul(step)));
+ }
+ toAbort.removeAll(simplex);
+ functionExecutor.abort(toAbort);
+ simplexComputed = false;
+ coordinateAcceptance++;
+
+ } else {
+ log.debug("Coordinate search unsuccessful, halving step.");
+ step /= 2;
+ reductionFallback++;
}
- toAbort.removeAll(simplex);
- functionExecutor.abort(toAbort);
- simplexComputed = false;
- coordinateAcceptance++;
-
} else {
- log.debug("Coordinate search unsuccessful, halving step.");
+ log.debug("Coordinate search not used, halving step.");
step /= 2;
reductionFallback++;
}
log.debug("Ending optimization step with simplex " + simplex);
+ continueOptimization = control.stepTaken(current, currentValue, simplex.get(0),
+ functionExecutor.getValue(simplex.get(0)), step);
+
if (Thread.interrupted()) {
throw new InterruptedException();
}
- } while (control.stepTaken(current, currentValue, simplex.get(0),
- functionExecutor.getValue(simplex.get(0)), step));
+ }
} catch (InterruptedException e) {
log.info("Optimization was interrupted with InterruptedException");
for (int j = 0; j < i; j++) {
value -= MathUtil.pow2(coordinates[j]);
}
- value = Math.sqrt(value);
+ value = MathUtil.safeSqrt(value);
coordinates[i] = value;
pattern.add(new Point(coordinates));
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.unit.UnitGroup;
/**
* A parameter of a rocket or simulation that can be optimized
*/
public double computeValue(Simulation simulation) throws OptimizationException;
+
+ /**
+ * Return the unit group associated with the computed value.
+ * @return the unit group of the computed value.
+ */
+ public UnitGroup getUnitGroup();
+
}
@Override
public double evaluate(Point point) throws InterruptedException, OptimizationException {
- System.out.println("Evaluating function at point " + point);
-
/*
* parameterValue is the computed parameter value (e.g. altitude)
* goalValue is the value that needs to be minimized
*/
double goalValue, parameterValue;
-
- log.verbose("Computing optimization function value at point " + point);
+ log.debug("Computing optimization function value at point " + point);
// Create the new simulation based on the point
double[] p = point.asArray();
// Check whether the point is within the simulation domain
- Pair<Double, Double> d = domain.getDistanceToDomain(simulation);
+ Pair<Double, Value> d = domain.getDistanceToDomain(simulation);
double distance = d.getU();
- double referenceValue = d.getV();
+ Value referenceValue = d.getV();
if (distance > 0 || Double.isNaN(distance)) {
if (Double.isNaN(distance)) {
goalValue = Double.MAX_VALUE;
} else {
goalValue = (distance + 1) * OUTSIDE_DOMAIN_SCALE;
}
- log.verbose("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
- System.out.println("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
+ log.debug("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
- fireEvent(simulation, point, referenceValue, Double.NaN, goalValue);
+ fireEvent(simulation, point, referenceValue, null, goalValue);
return goalValue;
}
}
log.verbose("Parameter value at point " + point + " is " + parameterValue + ", goal function value=" + goalValue);
- System.out.println("Parameter value at point " + point + " is " + parameterValue + ", goal function value=" + goalValue);
- fireEvent(simulation, point, referenceValue, parameterValue, goalValue);
+ fireEvent(simulation, point, referenceValue, new Value(parameterValue, parameter.getUnitGroup().getDefaultUnit()),
+ goalValue);
return goalValue;
}
- private void fireEvent(Simulation simulation, Point p, double domainReference, double parameterValue, double goalValue)
+ private void fireEvent(Simulation simulation, Point p, Value domainReference, Value parameterValue, double goalValue)
throws OptimizationException {
if (listeners.isEmpty()) {
* Called after successful function evaluation.
*
* @param point the optimization point.
- * @param state the values to which the rocket has been modified, in the order of "point".
- * @param domainReference the domain reference value (or NaN if unavailable)
+ * @param state the values to which the rocket has been modified in SI units, in the order of "point".
+ * @param domainReference the domain reference description (or null if unavailable)
* @param parameterValue the parameter value (or NaN if unavailable)
* @param goalValue the goal value (return value of the function)
*/
- public void evaluated(Point point, Value[] state, double domainReference, double parameterValue, double goalValue);
+ public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue);
}
package net.sf.openrocket.optimization.rocketoptimization;
import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.Pair;
/**
* is NaN, the simulation is outside of the domain.
*
* @param simulation the simulation to check.
- * @return the double parameter is the domain indication;
+ * @return A pair indicating the status. The first element is the reference value,
* a negative value or zero if the simulation is in the domain,
- * a positive value or NaN if not. The second is a human-readable
+ * a positive value or NaN if not. The second is the value
* indication of the domain (may be null).
*/
- public Pair<Double, Double> getDistanceToDomain(Simulation simulation);
+ public Pair<Double, Value> getDistanceToDomain(Simulation simulation);
+
}
public interface SimulationModifier extends ChangeSource {
/**
- * Return a name describing this modifier.
+ * Return a short name describing this modifier.
* @return a name describing this modifier.
*/
public String getName();
+ /**
+ * Return a longer description describing this modifiers.
+ * @return a description of the modifier.
+ */
+ public String getDescription();
/**
* Return the object this modifier is related to. This is for example the
*/
public void modify(Simulation simulation, double scaledValue) throws OptimizationException;
+
+ /**
+ * Compare whether this SimulationModifier is equivalent to another simulation modifier.
+ * "Equivalent" means that the simulation modifier corresponds to the same modification in
+ * another rocket instance (e.g. the same modification on another rocket component that
+ * has the same component ID).
+ */
+ @Override
+ public boolean equals(Object obj);
}
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
+import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.Pair;
/**
public class IdentitySimulationDomain implements SimulationDomain {
@Override
- public Pair<Double, Double> getDistanceToDomain(Simulation simulation) {
- return new Pair<Double, Double>(-1.0, Double.NaN);
+ public Pair<Double, Value> getDistanceToDomain(Simulation simulation) {
+ return new Pair<Double, Value>(-1.0, null);
}
}
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Pair;
import net.sf.openrocket.util.Prefs;
/**
- * A simulation domain that limits the requires stability of the rocket.
+ * A simulation domain that limits the required stability of the rocket.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class StabilityDomain implements SimulationDomain {
- /*
- * TODO: HIGH: Should this rather inspect stability during flight
- */
-
- private final double limit;
- private final boolean absolute;
+ private final double minimum;
+ private final boolean minAbsolute;
+ private final double maximum;
+ private final boolean maxAbsolute;
- public StabilityDomain(double limit, boolean absolute) {
- this.limit = limit;
- this.absolute = absolute;
+ /**
+ * Sole constructor.
+ *
+ * @param minimum minimum stability requirement (or <code>NaN</code> for no limit)
+ * @param minAbsolute <code>true</code> if minimum is an absolute SI measurement,
+ * <code>false</code> if it is relative to the rocket caliber
+ * @param maximum maximum stability requirement (or <code>NaN</code> for no limit)
+ * @param maxAbsolute <code>true</code> if maximum is an absolute SI measurement,
+ * <code>false</code> if it is relative to the rocket caliber
+ */
+ public StabilityDomain(double minimum, boolean minAbsolute, double maximum, boolean maxAbsolute) {
+ super();
+ this.minimum = minimum;
+ this.minAbsolute = minAbsolute;
+ this.maximum = maximum;
+ this.maxAbsolute = maxAbsolute;
}
+
+
@Override
- public Pair<Double, Double> getDistanceToDomain(Simulation simulation) {
+ public Pair<Double, Value> getDistanceToDomain(Simulation simulation) {
Coordinate cp, cg;
double cpx, cgx;
- double reference;
+ double absolute;
+ double relative;
/*
* These are instantiated each time because this class must be thread-safe.
// Calculate the reference (absolute or relative)
- reference = cpx - cgx;
- if (!absolute) {
- double diameter = 0;
- for (RocketComponent c : configuration) {
- if (c instanceof SymmetricComponent) {
- double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
- double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
- diameter = MathUtil.max(diameter, d1, d2);
- }
+ absolute = cpx - cgx;
+
+ double diameter = 0;
+ for (RocketComponent c : configuration) {
+ if (c instanceof SymmetricComponent) {
+ double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
+ double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
+ diameter = MathUtil.max(diameter, d1, d2);
}
-
- reference = (cpx - cgx) / diameter;
}
+ relative = absolute / diameter;
- System.out.println("DOMAIN: limit=" + limit + " reference=" + reference + " result=" + (limit - reference));
+
+ Value desc;
+ if (minAbsolute && maxAbsolute) {
+ desc = new Value(absolute, UnitGroup.UNITS_LENGTH);
+ } else {
+ desc = new Value(relative, UnitGroup.UNITS_STABILITY_CALIBERS);
+ }
- return new Pair<Double, Double>(limit - reference, reference);
+ double ref;
+ if (minAbsolute) {
+ ref = minimum - absolute;
+ if (ref > 0) {
+ return new Pair<Double, Value>(ref, desc);
+ }
+ } else {
+ ref = minimum - relative;
+ if (ref > 0) {
+ return new Pair<Double, Value>(ref, desc);
+ }
+ }
+
+ if (maxAbsolute) {
+ ref = absolute - maximum;
+ if (ref > 0) {
+ return new Pair<Double, Value>(ref, desc);
+ }
+ } else {
+ ref = relative - maximum;
+ if (ref > 0) {
+ return new Pair<Double, Value>(ref, desc);
+ }
+ }
+
+ return new Pair<Double, Value>(0.0, desc);
}
-
}
public abstract class AbstractSimulationModifier implements SimulationModifier {
private final String name;
+ private final String description;
private final Object relatedObject;
private final UnitGroup unitGroup;
/**
* Sole constructor.
*
- * @param modifierName the name of this modifier (returned by {@link #getName()})
- * @param relatedObject the related object (returned by {@link #getRelatedObject()})
- * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
+ * @param modifierName the name of this modifier (returned by {@link #getName()})
+ * @param modifierDescription the description of this modifier (returned by {@link #getDescription()})
+ * @param relatedObject the related object (returned by {@link #getRelatedObject()})
+ * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
*/
- public AbstractSimulationModifier(String modifierName, Object relatedObject, UnitGroup unitGroup) {
+ public AbstractSimulationModifier(String modifierName, String modifierDescription, Object relatedObject,
+ UnitGroup unitGroup) {
this.name = modifierName;
+ this.description = modifierDescription;
this.relatedObject = relatedObject;
this.unitGroup = unitGroup;
+
+ if (this.name == null || this.description == null || this.relatedObject == null || this.unitGroup == null) {
+ throw new IllegalArgumentException("null value provided:" +
+ " name=" + this.name + " description=" + description + " relatedObject=" + relatedObject +
+ " unitGroup=" + unitGroup);
+ }
}
return name;
}
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
@Override
public Object getRelatedObject() {
return relatedObject;
}
}
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+
+ AbstractSimulationModifier other = (AbstractSimulationModifier) obj;
+ if (!this.description.equals(other.description))
+ return false;
+ if (!this.name.equals(other.name))
+ return false;
+ if (!this.relatedObject.equals(other.relatedObject))
+ return false;
+ if (!this.unitGroup.equals(other.unitGroup))
+ return false;
+
+ return true;
+ }
+
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (description.hashCode());
+ result = prime * result + (name.hashCode());
+ result = prime * result + (relatedObject.hashCode());
+ result = prime * result + (unitGroup.hashCode());
+ return result;
+ }
+
+
}
/**
* Sole constructor.
*
- * @param modifierName the name of this modifier (returned by {@link #getName()})
- * @param relatedObject the related object (returned by {@link #getRelatedObject()})
- * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
- * @param multiplier the multiplier by which the value returned by the getter is multiplied
- * to obtain the desired value
- * @param componentClass the RocketComponent class type that is being modified
- * @param componentId the ID of the component to modify
- * @param methodName the base name of the getter/setter methods (without "get"/"set")
+ * @param modifierName the name of this modifier (returned by {@link #getName()})
+ * @param modifierDescription the description of this modifier (returned by {@link #getDescription()})
+ * @param relatedObject the related object (returned by {@link #getRelatedObject()})
+ * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
+ * @param multiplier the multiplier by which the value returned by the getter is multiplied
+ * to obtain the desired value
+ * @param componentClass the RocketComponent class type that is being modified
+ * @param componentId the ID of the component to modify
+ * @param methodName the base name of the getter/setter methods (without "get"/"set")
*/
- public GenericComponentModifier(String modifierName, Object relatedObject, UnitGroup unitGroup,
+ public GenericComponentModifier(String modifierName, String modifierDescription, Object relatedObject, UnitGroup unitGroup,
double multiplier, Class<? extends RocketComponent> componentClass, String componentId, String methodName) {
- super(modifierName, relatedObject, unitGroup, multiplier, componentClass, methodName);
+ super(modifierName, modifierDescription, relatedObject, unitGroup, multiplier, componentClass, methodName);
this.componentClass = componentClass;
this.componentId = componentId;
* Sole constructor.
*
* @param modifierName the name of this modifier (returned by {@link #getName()})
+ * @param modifierDescription the description of this modifier (returned by {@link #getDescription()})
* @param relatedObject the related object (returned by {@link #getRelatedObject()})
* @param unitGroup the unit group (returned by {@link #getUnitGroup()})
* @param multiplier the multiplier by which the value returned by the getter is multiplied
* @param modifiedClass the class type that {@link #getModifiedObject(Simulation)} returns
* @param methodName the base name of the getter/setter methods (without "get"/"set")
*/
- public GenericModifier(String modifierName, Object relatedObject, UnitGroup unitGroup, double multiplier,
- Class<? extends T> modifiedClass, String methodName) {
- super(modifierName, relatedObject, unitGroup);
+ public GenericModifier(String modifierName, String modifierDescription, Object relatedObject, UnitGroup unitGroup,
+ double multiplier, Class<? extends T> modifiedClass, String methodName) {
+ super(modifierName, modifierDescription, relatedObject, unitGroup);
this.multiplier = multiplier;
this.modifiedClass = modifiedClass;
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+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.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the speed the rocket hits the ground.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class GroundHitVelocityParameter implements OptimizableParameter {
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ @Override
+ public String getName() {
+ return trans.get("name");
+ }
+
+ @Override
+ public double computeValue(Simulation simulation) throws OptimizationException {
+ try {
+ log.debug("Running simulation to evaluate ground hit speed");
+ simulation.simulate();
+ double value = simulation.getSimulatedData().getBranch(0).getLast(FlightDataType.TYPE_VELOCITY_TOTAL);
+ log.debug("Ground hit speed was " + value);
+ return value;
+ } catch (MotorIgnitionException e) {
+ // A problem with motor ignition will cause optimization to fail
+ throw new OptimizationException(e);
+ } catch (SimulationLaunchException e) {
+ // Other launch exceptions result in zero altitude
+ return Double.NaN;
+ } catch (SimulationException e) {
+ // Other exceptions fail
+ throw new OptimizationException(e);
+ }
+ }
+
+ @Override
+ public UnitGroup getUnitGroup() {
+ return UnitGroup.UNITS_VELOCITY;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+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.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the distance where a rocket lands.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class LandingDistanceParameter implements OptimizableParameter {
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ @Override
+ public String getName() {
+ return trans.get("name");
+ }
+
+ @Override
+ public double computeValue(Simulation simulation) throws OptimizationException {
+ try {
+ log.debug("Running simulation to evaluate rocket landing distance");
+ simulation.simulate();
+ double value = simulation.getSimulatedData().getBranch(0).getLast(FlightDataType.TYPE_POSITION_XY);
+ log.debug("Landing distance was " + value);
+ return value;
+ } catch (MotorIgnitionException e) {
+ // A problem with motor ignition will cause optimization to fail
+ throw new OptimizationException(e);
+ } catch (SimulationLaunchException e) {
+ // Other launch exceptions result in zero altitude
+ return Double.NaN;
+ } catch (SimulationException e) {
+ // Other exceptions fail
+ throw new OptimizationException(e);
+ }
+ }
+
+ @Override
+ public UnitGroup getUnitGroup() {
+ return UnitGroup.UNITS_DISTANCE;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+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.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the maximum acceleration during a simulated flight.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MaximumAccelerationParameter implements OptimizableParameter {
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ @Override
+ public String getName() {
+ return trans.get("name");
+ }
+
+ @Override
+ public double computeValue(Simulation simulation) throws OptimizationException {
+ try {
+ log.debug("Running simulation to evaluate maximum acceleration");
+ simulation.simulate(new ApogeeEndListener());
+ double value = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ACCELERATION_TOTAL);
+ log.debug("Maximum acceleration was " + value);
+ return value;
+ } catch (MotorIgnitionException e) {
+ // A problem with motor ignition will cause optimization to fail
+ throw new OptimizationException(e);
+ } catch (SimulationLaunchException e) {
+ // Other launch exceptions result in zero velocity
+ return Double.NaN;
+ } catch (SimulationException e) {
+ // Other exceptions fail
+ throw new OptimizationException(e);
+ }
+ }
+
+ @Override
+ public UnitGroup getUnitGroup() {
+ return UnitGroup.UNITS_ACCELERATION;
+ }
+
+}
package net.sf.openrocket.optimization.rocketoptimization.parameters;
import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
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.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
/**
* An optimization parameter that computes the maximum altitude of a simulated flight.
*/
public class MaximumAltitudeParameter implements OptimizableParameter {
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
@Override
public String getName() {
- return "Maximum altitude";
+ return trans.get("name");
}
@Override
public double computeValue(Simulation simulation) throws OptimizationException {
try {
- System.out.println("Running simulation");
+ log.debug("Running simulation to evaluate apogee altitude");
simulation.simulate(new ApogeeEndListener());
- System.out.println("Maximum altitude was " + simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE));
+ 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) {
+ // A problem with motor ignition will cause optimization to fail
+ throw new OptimizationException(e);
+ } catch (SimulationLaunchException e) {
+ // Other launch exceptions result in zero altitude
+ return 0.0;
} catch (SimulationException e) {
+ // Other exceptions fail
throw new OptimizationException(e);
}
}
+ @Override
+ public UnitGroup getUnitGroup() {
+ return UnitGroup.UNITS_DISTANCE;
+ }
+
}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+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.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the maximum velocity during a simulated flight.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MaximumVelocityParameter implements OptimizableParameter {
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ @Override
+ public String getName() {
+ return trans.get("name");
+ }
+
+ @Override
+ public double computeValue(Simulation simulation) throws OptimizationException {
+ try {
+ log.debug("Running simulation to evaluate maximum velocity");
+ simulation.simulate(new ApogeeEndListener());
+ double value = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_VELOCITY_TOTAL);
+ log.debug("Maximum velocity was " + value);
+ return value;
+ } catch (MotorIgnitionException e) {
+ // A problem with motor ignition will cause optimization to fail
+ throw new OptimizationException(e);
+ } catch (SimulationLaunchException e) {
+ // Other launch exceptions result in zero velocity
+ return Double.NaN;
+ } catch (SimulationException e) {
+ // Other exceptions fail
+ throw new OptimizationException(e);
+ }
+ }
+
+ @Override
+ public UnitGroup getUnitGroup() {
+ return UnitGroup.UNITS_VELOCITY;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
+import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.aerodynamics.FlightConditions;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.masscalc.BasicMassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+
+/**
+ * An optimization parameter that computes either the absolute or relative stability of a rocket.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class StabilityParameter implements OptimizableParameter {
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+
+ private final boolean absolute;
+
+ public StabilityParameter(boolean absolute) {
+ this.absolute = absolute;
+ }
+
+
+ @Override
+ public String getName() {
+ return trans.get("name") + " (" + getUnitGroup().getDefaultUnit().getUnit() + ")";
+ }
+
+ @Override
+ public double computeValue(Simulation simulation) throws OptimizationException {
+ Coordinate cp, cg;
+ double cpx, cgx;
+ double stability;
+
+ log.debug("Calculating stability of simulation, absolute=" + absolute);
+
+ /*
+ * These are instantiated each time because this class must be thread-safe.
+ * Caching would in any case be inefficient since the rocket changes all the time.
+ */
+ AerodynamicCalculator aerodynamicCalculator = new BarrowmanCalculator();
+ MassCalculator massCalculator = new BasicMassCalculator();
+
+
+ Configuration configuration = simulation.getConfiguration();
+ FlightConditions conditions = new FlightConditions(configuration);
+ conditions.setMach(Prefs.getDefaultMach());
+ conditions.setAOA(0);
+ conditions.setRollRate(0);
+
+ cp = aerodynamicCalculator.getWorstCP(configuration, conditions, null);
+ cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS);
+
+ if (cp.weight > 0.000001)
+ cpx = cp.x;
+ else
+ cpx = Double.NaN;
+
+ if (cg.weight > 0.000001)
+ cgx = cg.x;
+ else
+ cgx = Double.NaN;
+
+
+ // Calculate the reference (absolute or relative)
+ stability = cpx - cgx;
+
+ if (!absolute) {
+ double diameter = 0;
+ for (RocketComponent c : configuration) {
+ if (c instanceof SymmetricComponent) {
+ double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
+ double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
+ diameter = MathUtil.max(diameter, d1, d2);
+ }
+ }
+ stability = stability / diameter;
+ }
+
+ log.debug("Resulting stability is " + stability + ", absolute=" + absolute);
+
+ return stability;
+ }
+
+ @Override
+ public UnitGroup getUnitGroup() {
+ if (absolute) {
+ return UnitGroup.UNITS_LENGTH;
+ } else {
+ return UnitGroup.UNITS_STABILITY_CALIBERS;
+ }
+ }
+
+}
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.GroundHitVelocityParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.LandingDistanceParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumAccelerationParameter;
import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumAltitudeParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumVelocityParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.StabilityParameter;
/**
* Default implementation for optimization parameter service.
List<OptimizableParameter> list = new ArrayList<OptimizableParameter>();
list.add(new MaximumAltitudeParameter());
+ list.add(new MaximumVelocityParameter());
+ list.add(new MaximumAccelerationParameter());
+ list.add(new StabilityParameter(false));
+ list.add(new StabilityParameter(true));
+ list.add(new GroundHitVelocityParameter());
+ list.add(new LandingDistanceParameter());
return list;
}
import java.util.Map;
import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.optimization.rocketoptimization.modifiers.GenericComponentModifier;
import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.InternalComponent;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.MassComponent;
+import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.NoseCone;
+import net.sf.openrocket.rocketcomponent.Parachute;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice.DeployEvent;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Streamer;
import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Reflection;
+import net.sf.openrocket.util.Reflection.Method;
public class DefaultSimulationModifierService implements SimulationModifierService {
private static final Translator trans = Application.getTranslator();
+ private static final double DEFAULT_RANGE_MULTIPLIER = 2.0;
+
+
private static final Map<Class<?>, List<ModifierDefinition>> definitions = new HashMap<Class<?>, List<ModifierDefinition>>();
static {
//addModifier("optimization.modifier.", unitGroup, multiplier, componentClass, methodName);
*/
addModifier("optimization.modifier.nosecone.length", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Length");
- addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "AftRadius");
- addModifier("optimization.modifier.nosecone.thickness", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Thickness");
+ addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "AftRadius", "isAftRadiusAutomatic");
+ addModifier("optimization.modifier.nosecone.thickness", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Thickness", "isFilled");
addModifier("optimization.modifier.transition.length", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Length");
- addModifier("optimization.modifier.transition.forediameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "ForeRadius");
- addModifier("optimization.modifier.transition.aftdiameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "AftRadius");
- addModifier("optimization.modifier.transition.thickness", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Thickness");
+ addModifier("optimization.modifier.transition.forediameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "ForeRadius", "isForeRadiusAutomatic");
+ addModifier("optimization.modifier.transition.aftdiameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "AftRadius", "isAftRadiusAutomatic");
+ addModifier("optimization.modifier.transition.thickness", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Thickness", "isFilled");
addModifier("optimization.modifier.bodytube.length", UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, "Length");
- addModifier("optimization.modifier.bodytube.outerDiameter", UnitGroup.UNITS_LENGTH, 2.0, BodyTube.class, "OuterRadius");
- addModifier("optimization.modifier.bodytube.thickness", UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, "Thickness");
+ addModifier("optimization.modifier.bodytube.outerDiameter", UnitGroup.UNITS_LENGTH, 2.0, BodyTube.class, "OuterRadius", "isOuterRadiusAutomatic");
+ addModifier("optimization.modifier.bodytube.thickness", UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, "Thickness", "isFilled");
+
+ addModifier("optimization.modifier.trapezoidfinset.rootChord", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "RootChord");
+ addModifier("optimization.modifier.trapezoidfinset.tipChord", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "TipChord");
+ addModifier("optimization.modifier.trapezoidfinset.sweep", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "Sweep");
+ addModifier("optimization.modifier.trapezoidfinset.height", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "Height");
+ addModifier("optimization.modifier.finset.cant", UnitGroup.UNITS_ANGLE, 1.0, TrapezoidFinSet.class, "CantAngle");
+
+ addModifier("optimization.modifier.ellipticalfinset.length", UnitGroup.UNITS_LENGTH, 1.0, EllipticalFinSet.class, "Length");
+ addModifier("optimization.modifier.ellipticalfinset.height", UnitGroup.UNITS_LENGTH, 1.0, EllipticalFinSet.class, "Height");
+ addModifier("optimization.modifier.finset.cant", UnitGroup.UNITS_ANGLE, 1.0, EllipticalFinSet.class, "CantAngle");
+
+ addModifier("optimization.modifier.finset.cant", UnitGroup.UNITS_ANGLE, 1.0, FreeformFinSet.class, "CantAngle");
+
+ addModifier("optimization.modifier.launchlug.length", UnitGroup.UNITS_LENGTH, 1.0, LaunchLug.class, "Length");
+ addModifier("optimization.modifier.launchlug.outerDiameter", UnitGroup.UNITS_LENGTH, 2.0, LaunchLug.class, "OuterRadius");
+ addModifier("optimization.modifier.launchlug.thickness", UnitGroup.UNITS_LENGTH, 1.0, LaunchLug.class, "Thickness");
+ addModifier("optimization.modifier.masscomponent.mass", UnitGroup.UNITS_MASS, 1.0, MassComponent.class, "ComponentMass");
+
+ addModifier("optimization.modifier.parachute.diameter", UnitGroup.UNITS_LENGTH, 1.0, Parachute.class, "Diameter");
+ addModifier("optimization.modifier.parachute.coefficient", UnitGroup.UNITS_NONE, 1.0, Parachute.class, "CD");
+
+ addModifier("optimization.modifier.streamer.length", UnitGroup.UNITS_LENGTH, 1.0, Streamer.class, "StripLength");
+ addModifier("optimization.modifier.streamer.width", UnitGroup.UNITS_LENGTH, 1.0, Streamer.class, "StripWidth");
+ addModifier("optimization.modifier.streamer.aspectRatio", UnitGroup.UNITS_NONE, 1.0, Streamer.class, "AspectRatio");
+ addModifier("optimization.modifier.streamer.coefficient", UnitGroup.UNITS_NONE, 1.0, Streamer.class, "CD", "isCDAutomatic");
+
+ }
+
+ private static void addModifier(String modifierNameKey, UnitGroup unitGroup, double multiplier,
+ Class<? extends RocketComponent> componentClass, String methodName) {
+ addModifier(modifierNameKey, unitGroup, multiplier, componentClass, methodName, null);
}
private static void addModifier(String modifierNameKey, UnitGroup unitGroup, double multiplier,
- Class<? extends RocketComponent> componentClass, String methodName) {
+ Class<? extends RocketComponent> componentClass, String methodName, String autoMethod) {
+
+ String modifierDescriptionKey = modifierNameKey + ".desc";
List<ModifierDefinition> list = definitions.get(componentClass);
if (list == null) {
definitions.put(componentClass, list);
}
- ModifierDefinition definition = new ModifierDefinition(modifierNameKey, unitGroup, multiplier, componentClass, methodName);
+ ModifierDefinition definition = new ModifierDefinition(modifierNameKey, modifierDescriptionKey, unitGroup,
+ multiplier, componentClass, methodName, autoMethod);
list.add(definition);
}
List<SimulationModifier> modifiers = new ArrayList<SimulationModifier>();
Rocket rocket = document.getRocket();
+
+ // Simulation is used to calculate default min/max values
+ Simulation simulation = new Simulation(rocket);
+ simulation.getConfiguration().setMotorConfigurationID(null);
+
for (RocketComponent c : rocket) {
// Attribute modifiers
List<ModifierDefinition> list = definitions.get(c.getClass());
if (list != null) {
for (ModifierDefinition def : list) {
+
+ // Ignore modifier if value is set to automatic
+ if (def.autoMethod != null) {
+ Method m = Reflection.findMethod(c.getClass(), def.autoMethod);
+ if ((Boolean) m.invoke(c)) {
+ continue;
+ }
+ }
+
SimulationModifier mod = new GenericComponentModifier(
- trans.get(def.modifierNameKey), c, def.unitGroup, def.multiplier, def.componentClass,
- c.getID(), def.methodName);
+ trans.get(def.modifierNameKey), trans.get(def.modifierDescriptionKey), c, def.unitGroup,
+ def.multiplier, def.componentClass, c.getID(), def.methodName);
+ setDefaultMinMax(mod, simulation);
modifiers.add(mod);
}
}
- // TODO: HIGH: Conditional modifiers (overrides)
+
+ // Add override modifiers if mass/CG is overridden
+ if (c.isMassOverridden()) {
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.rocketcomponent.overrideMass"),
+ trans.get("optimization.modifier.rocketcomponent.overrideMass.desc"),
+ c, UnitGroup.UNITS_MASS,
+ 1.0, c.getClass(), c.getID(), "OverrideMass");
+ setDefaultMinMax(mod, simulation);
+ modifiers.add(mod);
+ }
+ if (c.isCGOverridden()) {
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.rocketcomponent.overrideCG"),
+ trans.get("optimization.modifier.rocketcomponent.overrideCG.desc"),
+ c, UnitGroup.UNITS_LENGTH,
+ 1.0, c.getClass(), c.getID(), "OverrideCGX");
+ mod.setMinValue(0);
+ mod.setMaxValue(c.getLength());
+ modifiers.add(mod);
+ }
+
+
+ // Conditional motor mount parameters
+ if (c instanceof MotorMount) {
+ MotorMount mount = (MotorMount) c;
+ if (mount.isMotorMount()) {
+
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.motormount.overhang"),
+ trans.get("optimization.modifier.motormount.overhang.desc"),
+ c, UnitGroup.UNITS_LENGTH,
+ 1.0, c.getClass(), c.getID(), "MotorOverhang");
+ setDefaultMinMax(mod, simulation);
+ modifiers.add(mod);
+
+ mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.motormount.delay"),
+ trans.get("optimization.modifier.motormount.delay.desc"),
+ c, UnitGroup.UNITS_SHORT_TIME,
+ 1.0, c.getClass(), c.getID(), "IgnitionDelay");
+ mod.setMinValue(0);
+ mod.setMaxValue(5);
+ modifiers.add(mod);
+
+ }
+ }
- // TODO: Transition / Nose cone shape parameter (conditional)
+
+ // Inner component positioning
+ if (c instanceof InternalComponent) {
+ RocketComponent parent = c.getParent();
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.internalcomponent.position"),
+ trans.get("optimization.modifier.internalcomponent.position.desc"),
+ c, UnitGroup.UNITS_LENGTH,
+ 1.0, c.getClass(), c.getID(), "PositionValue");
+ mod.setMinValue(0);
+ mod.setMaxValue(parent.getLength());
+ modifiers.add(mod);
+ }
+
+
+ // Custom min/max for fin set position
+ if (c instanceof FinSet) {
+ RocketComponent parent = c.getParent();
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.finset.position"),
+ trans.get("optimization.modifier.finset.position.desc"),
+ c, UnitGroup.UNITS_LENGTH,
+ 1.0, c.getClass(), c.getID(), "PositionValue");
+ mod.setMinValue(0);
+ mod.setMaxValue(parent.getLength());
+ modifiers.add(mod);
+ }
+
+
+ // Custom min/max for launch lug position
+ if (c instanceof LaunchLug) {
+ RocketComponent parent = c.getParent();
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.launchlug.position"),
+ trans.get("optimization.modifier.launchlug.position.desc"),
+ c, UnitGroup.UNITS_LENGTH,
+ 1.0, c.getClass(), c.getID(), "PositionValue");
+ mod.setMinValue(0);
+ mod.setMaxValue(parent.getLength());
+ modifiers.add(mod);
+ }
+
+
+ // Recovery device deployment altitude and delay
+ if (c instanceof RecoveryDevice) {
+ RecoveryDevice device = (RecoveryDevice) c;
+
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.recoverydevice.deployDelay"),
+ trans.get("optimization.modifier.recoverydevice.deployDelay.desc"),
+ c, UnitGroup.UNITS_SHORT_TIME,
+ 1.0, c.getClass(), c.getID(), "DeployDelay");
+ mod.setMinValue(0);
+ mod.setMaxValue(10);
+ modifiers.add(mod);
+
+ if (device.getDeployEvent() == DeployEvent.ALTITUDE) {
+ mod = new GenericComponentModifier(
+ trans.get("optimization.modifier.recoverydevice.deployAltitude"),
+ trans.get("optimization.modifier.recoverydevice.deployAltitude.desc"),
+ c, UnitGroup.UNITS_DISTANCE,
+ 1.0, c.getClass(), c.getID(), "DeployAltitude");
+ setDefaultMinMax(mod, simulation);
+ modifiers.add(mod);
+ }
+ }
+
+
+ // Conditional shape parameter of Transition
+ if (c instanceof Transition) {
+ Transition transition = (Transition) c;
+ Transition.Shape shape = transition.getType();
+ if (shape.usesParameter()) {
+ SimulationModifier mod = new GenericComponentModifier(
+ trans.get("optimization.modifier." + c.getClass().getSimpleName().toLowerCase() + ".shapeparameter"),
+ trans.get("optimization.modifier." + c.getClass().getSimpleName().toLowerCase() + ".shapeparameter.desc"),
+ c, UnitGroup.UNITS_NONE,
+ 1.0, c.getClass(), c.getID(), "ShapeParameter");
+ mod.setMinValue(shape.minParameter());
+ mod.setMaxValue(shape.maxParameter());
+ modifiers.add(mod);
+ }
+ }
}
return modifiers;
}
-
+ private void setDefaultMinMax(SimulationModifier mod, Simulation simulation) {
+ try {
+ double current = mod.getCurrentSIValue(simulation);
+ mod.setMinValue(current / DEFAULT_RANGE_MULTIPLIER);
+ mod.setMaxValue(current * DEFAULT_RANGE_MULTIPLIER);
+ } catch (OptimizationException e) {
+ throw new BugException("Simulation modifier threw exception", e);
+ }
+ }
+
+
/*
* String modifierName, Object relatedObject, UnitGroup unitGroup,
double multiplier, Class<? extends RocketComponent> componentClass, String componentId, String methodName
private static class ModifierDefinition {
private final String modifierNameKey;
+ private final String modifierDescriptionKey;
private final UnitGroup unitGroup;
private final double multiplier;
private final Class<? extends RocketComponent> componentClass;
private final String methodName;
+ private final String autoMethod;
- public ModifierDefinition(String modifierNameKey, UnitGroup unitGroup, double multiplier,
- Class<? extends RocketComponent> componentClass, String methodName) {
- super();
+ public ModifierDefinition(String modifierNameKey, String modifierDescriptionKey, UnitGroup unitGroup,
+ double multiplier, Class<? extends RocketComponent> componentClass, String methodName, String autoMethod) {
this.modifierNameKey = modifierNameKey;
+ this.modifierDescriptionKey = modifierDescriptionKey;
this.unitGroup = unitGroup;
this.multiplier = multiplier;
this.componentClass = componentClass;
this.methodName = methodName;
+ this.autoMethod = autoMethod;
}
}
/**
* Perform a deep-clone. The object references are also cloned and no
- * listeners are listening on the cloned object.
+ * listeners are listening on the cloned object. The rocket instance remains the same.
*/
@Override
public Configuration clone() {
double radius = getBodyRadius();
- return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius));
+ return fins * (inertia + MathUtil.pow2(MathUtil.safeSqrt(h2) + radius));
}
double h = getSpan();
if (MathUtil.equals(w * h, 0)) {
- h = Math.sqrt(area);
+ h = MathUtil.safeSqrt(area);
} else {
- h = Math.sqrt(h * area / w);
+ h = MathUtil.safeSqrt(h * area / w);
}
if (fins == 1)
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.MathUtil;
+/**
+ * This class represents a generic component that has a specific mass and an approximate shape.
+ * The mass is accessed via get/setComponentMass.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class MassComponent extends MassObject {
private static final Translator trans = Application.getTranslator();
-
+
private double mass = 0;
public class Parachute extends RecoveryDevice {
private static final Translator trans = Application.getTranslator();
-
+
public static final double DEFAULT_CD = 0.8;
private double diameter;
public void setArea(double area) {
if (MathUtil.equals(getArea(), area))
return;
- diameter = Math.sqrt(area / Math.PI) * 2;
+ diameter = MathUtil.safeSqrt(area / Math.PI) * 2;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
ratio = Math.max(ratio, 0.01);
double area = getArea();
- stripWidth = Math.sqrt(area / ratio);
+ stripWidth = MathUtil.safeSqrt(area / ratio);
stripLength = ratio * stripWidth;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
return;
double ratio = Math.max(getAspectRatio(), 0.01);
- stripWidth = Math.sqrt(area / ratio);
+ stripWidth = MathUtil.safeSqrt(area / ratio);
stripLength = ratio * stripWidth;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public static final double DEFAULT_RADIUS = 0.025;
public static final double DEFAULT_THICKNESS = 0.002;
- private static final int DIVISIONS = 100; // No. of divisions when integrating
+ private static final int DIVISIONS = 100; // No. of divisions when integrating
protected boolean filled = false;
protected double thickness = DEFAULT_THICKNESS;
-
+
// Cached data, default values signify not calculated
private double wetArea = -1;
private double planArea = -1;
private Coordinate cg = null;
-
+
public SymmetricComponent() {
super();
}
-
+
/**
* Return the component radius at position x.
* the component.
*/
public abstract double getRadius(double x);
+
+ @Override
public abstract double getInnerRadius(double x);
-
+
public abstract double getForeRadius();
+
public abstract boolean isForeRadiusAutomatic();
+
public abstract double getAftRadius();
+
public abstract boolean isAftRadiusAutomatic();
// Implement the Radial interface:
+ @Override
public final double getOuterRadius(double x) {
return getRadius(x);
}
}
-
+
/**
* Return the component wall thickness.
*/
public double getThickness() {
if (filled)
- return Math.max(getForeRadius(),getAftRadius());
- return Math.min(thickness,Math.max(getForeRadius(),getAftRadius()));
+ return Math.max(getForeRadius(), getAftRadius());
+ return Math.min(thickness, Math.max(getForeRadius(), getAftRadius()));
}
public void setThickness(double thickness) {
if ((this.thickness == thickness) && !filled)
return;
- this.thickness = MathUtil.clamp(thickness,0,Math.max(getForeRadius(),getAftRadius()));
+ this.thickness = MathUtil.clamp(thickness, 0, Math.max(getForeRadius(), getAftRadius()));
filled = false;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Returns whether the component is set as filled. If it is set filled, then the
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Adds component bounds at a number of points between 0...length.
*/
@Override
public Collection<Coordinate> getComponentBounds() {
List<Coordinate> list = new ArrayList<Coordinate>(20);
- for (int n=0; n<=5; n++) {
- double x = n*length/5;
+ for (int n = 0; n <= 5; n++) {
+ double x = n * length / 5;
double r = getRadius(x);
- addBound(list,x,r);
+ addBound(list, x, r);
}
return list;
}
-
+
/**
* Calculate volume of the component by integrating over the length of the component.
* The method caches the result, so subsequent calls are instant. Subclasses may
@Override
public double getLongitudinalUnitInertia() {
if (longitudinalInertia < 0) {
- if (getComponentVolume() > 0.0000001) // == 0.1cm^3
+ if (getComponentVolume() > 0.0000001) // == 0.1cm^3
integrateInertiaVolume();
else
integrateInertiaSurface();
* Performs integration over the length of the component and updates the cached variables.
*/
private void integrate() {
- double x,r1,r2;
+ double x, r1, r2;
double cgx;
// Check length > 0
return;
}
-
+
// Integrate for volume, CG, wetted area and planform area
- final double l = length/DIVISIONS;
- final double pil = Math.PI*l; // PI * l
- final double pil3 = Math.PI*l/3; // PI * l/3
+ final double l = length / DIVISIONS;
+ final double pil = Math.PI * l; // PI * l
+ final double pil3 = Math.PI * l / 3; // PI * l/3
r1 = getRadius(0);
x = 0;
wetArea = 0;
volume = 0;
cgx = 0;
- for (int n=1; n<=DIVISIONS; n++) {
+ for (int n = 1; n <= DIVISIONS; n++) {
/*
* r1 and r2 are the two radii
* x is the position of r1
* hyp is the length of the hypotenuse from r1 to r2
* height if the y-axis height of the component if not filled
*/
+
+ r2 = getRadius(x + l);
+ final double hyp = MathUtil.hypot(r2 - r1, l);
- r2 = getRadius(x+l);
- final double hyp = MathUtil.hypot(r2-r1, l);
-
-
+
// Volume differential elements
final double dV;
final double dFullV;
- dFullV = pil3*(r1*r1 + r1*r2 + r2*r2);
- if (filled || r1<thickness || r2<thickness) {
+ dFullV = pil3 * (r1 * r1 + r1 * r2 + r2 * r2);
+ if (filled || r1 < thickness || r2 < thickness) {
// Filled piece
dV = dFullV;
} else {
// Hollow piece
- final double height = thickness*hyp/l;
- dV = MathUtil.max(pil*height*(r1+r2-height), 0);
+ final double height = thickness * hyp / l;
+ dV = MathUtil.max(pil * height * (r1 + r2 - height), 0);
}
-
+
// Add to the volume-related components
volume += dV;
fullVolume += dFullV;
- cgx += (x+l/2)*dV;
+ cgx += (x + l / 2) * dV;
// Wetted area ( * PI at the end)
- wetArea += hyp*(r1+r2);
+ wetArea += hyp * (r1 + r2);
// Planform area & center
- final double p = l*(r1+r2);
+ final double p = l * (r1 + r2);
planArea += p;
- planCenter += (x+l/2)*p;
+ planCenter += (x + l / 2) * p;
// Update for next iteration
r1 = r2;
if (planArea > 0)
planCenter /= planArea;
- if (volume < 0.0000000001) { // 0.1 mm^3
+ if (volume < 0.0000000001) { // 0.1 mm^3
volume = 0;
- cg = new Coordinate(length/2, 0, 0, 0);
+ cg = new Coordinate(length / 2, 0, 0, 0);
} else {
// getComponentMass is safe now
// Use super.getComponentMass() to ensure only the transition shape mass
// is used, not the shoulders
- cg = new Coordinate(cgx/volume,0,0,super.getComponentMass());
+ cg = new Coordinate(cgx / volume, 0, 0, super.getComponentMass());
}
}
*/
private void integrateInertiaVolume() {
double x, r1, r2;
-
- final double l = length/DIVISIONS;
- final double pil = Math.PI*l; // PI * l
- final double pil3 = Math.PI*l/3; // PI * l/3
-
+
+ final double l = length / DIVISIONS;
+ final double pil = Math.PI * l; // PI * l
+ final double pil3 = Math.PI * l / 3; // PI * l/3
+
r1 = getRadius(0);
x = 0;
longitudinalInertia = 0;
double volume = 0;
- for (int n=1; n<=DIVISIONS; n++) {
+ for (int n = 1; n <= DIVISIONS; n++) {
/*
* r1 and r2 are the two radii, outer is their average
* x is the position of r1
* hyp is the length of the hypotenuse from r1 to r2
* height if the y-axis height of the component if not filled
*/
- r2 = getRadius(x+l);
- final double outer = (r1 + r2)/2;
-
+ r2 = getRadius(x + l);
+ final double outer = (r1 + r2) / 2;
+
// Volume differential elements
final double inner;
final double dV;
- if (filled || r1<thickness || r2<thickness) {
+ if (filled || r1 < thickness || r2 < thickness) {
inner = 0;
- dV = pil3*(r1*r1 + r1*r2 + r2*r2);
+ dV = pil3 * (r1 * r1 + r1 * r2 + r2 * r2);
} else {
- final double hyp = MathUtil.hypot(r2-r1, l);
- final double height = thickness*hyp/l;
- dV = pil*height*(r1+r2-height);
- inner = Math.max(outer-height, 0);
+ final double hyp = MathUtil.hypot(r2 - r1, l);
+ final double height = thickness * hyp / l;
+ dV = pil * height * (r1 + r2 - height);
+ inner = Math.max(outer - height, 0);
}
- rotationalInertia += dV * (pow2(outer) + pow2(inner))/2;
- longitudinalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l))/12
- + pow2(x+l/2));
+ rotationalInertia += dV * (pow2(outer) + pow2(inner)) / 2;
+ longitudinalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l)) / 12
+ + pow2(x + l / 2));
volume += dV;
x += l;
}
- if (MathUtil.equals(volume,0)) {
+ if (MathUtil.equals(volume, 0)) {
integrateInertiaSurface();
return;
}
rotationalInertia /= volume;
longitudinalInertia /= volume;
-
+
// Shift longitudinal inertia to CG
longitudinalInertia = Math.max(longitudinalInertia - pow2(getComponentCG().x), 0);
}
*/
private void integrateInertiaSurface() {
double x, r1, r2;
-
- final double l = length/DIVISIONS;
-
+
+ final double l = length / DIVISIONS;
+
r1 = getRadius(0);
+ System.out.println(r1);
x = 0;
+
longitudinalInertia = 0;
rotationalInertia = 0;
double surface = 0;
- for (int n=1; n<=DIVISIONS; n++) {
+ for (int n = 1; n <= DIVISIONS; n++) {
/*
* r1 and r2 are the two radii, outer is their average
* x is the position of r1
* hyp is the length of the hypotenuse from r1 to r2
* height if the y-axis height of the component if not filled
*/
- r2 = getRadius(x+l);
- final double hyp = MathUtil.hypot(r2-r1, l);
- final double outer = (r1 + r2)/2;
+ r2 = getRadius(x + l);
+ final double hyp = MathUtil.hypot(r2 - r1, l);
+ final double outer = (r1 + r2) / 2;
+
+ final double dS = hyp * (r1 + r2) * Math.PI;
- final double dS = hyp * (r1+r2) * Math.PI;
-
rotationalInertia += dS * pow2(outer);
- longitudinalInertia += dS * ((6 * pow2(outer) + pow2(l))/12 + pow2(x+l/2));
+ longitudinalInertia += dS * ((6 * pow2(outer) + pow2(l)) / 12 + pow2(x + l / 2));
surface += dS;
r1 = r2;
x += l;
}
-
+
if (MathUtil.equals(surface, 0)) {
longitudinalInertia = 0;
rotationalInertia = 0;
}
-
-
+
+
/**
* Invalidates the cached volume and CG information.
*/
}
-
+
/////////// Auto radius helper methods
-
+
/**
* Returns the automatic radius for this component towards the
* front of the rocket. The automatics will not search towards the
* match was not found.
*/
protected abstract double getFrontAutoRadius();
-
+
/**
* Returns the automatic radius for this component towards the
* end of the rocket. The automatics will not search towards the
protected abstract double getRearAutoRadius();
-
+
/**
* Return the previous symmetric component, or null if none exists.
* NOTE: This method currently assumes that there are no external
RocketComponent c;
for (c = this.getPreviousComponent(); c != null; c = c.getPreviousComponent()) {
if (c instanceof SymmetricComponent) {
- return (SymmetricComponent)c;
+ return (SymmetricComponent) c;
}
if (!(c instanceof Stage) &&
- (c.relativePosition == RocketComponent.Position.AFTER))
- return null; // Bad component type as "parent"
+ (c.relativePosition == RocketComponent.Position.AFTER))
+ return null; // Bad component type as "parent"
}
return null;
}
RocketComponent c;
for (c = this.getNextComponent(); c != null; c = c.getNextComponent()) {
if (c instanceof SymmetricComponent) {
- return (SymmetricComponent)c;
+ return (SymmetricComponent) c;
}
if (!(c instanceof Stage) &&
- (c.relativePosition == RocketComponent.Position.AFTER))
- return null; // Bad component type as "parent"
+ (c.relativePosition == RocketComponent.Position.AFTER))
+ return null; // Bad component type as "parent"
}
return null;
}
package net.sf.openrocket.rocketcomponent;
-import static java.lang.Math.*;
+import static java.lang.Math.sin;
import static net.sf.openrocket.util.MathUtil.*;
import java.util.Collection;
return CONICAL.getRadius(x, radius, length, param);
// Radius of circle is:
- double R = sqrt((pow2(length) + pow2(radius)) *
+ double R = MathUtil.safeSqrt((pow2(length) + pow2(radius)) *
(pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
double L = length / param;
// double R = (radius + length*length/(radius*param*param))/2;
- double y0 = sqrt(R * R - L * L);
- return sqrt(R * R - (L - x) * (L - x)) - y0;
+ double y0 = MathUtil.safeSqrt(R * R - L * L);
+ return MathUtil.safeSqrt(R * R - (L - x) * (L - x)) - y0;
}
},
assert x <= length;
assert radius >= 0;
x = x * radius / length;
- return sqrt(2 * radius * x - x * x); // radius/length * sphere
+ return MathUtil.safeSqrt(2 * radius * x - x * x); // radius/length * sphere
}
},
assert param <= 2;
double theta = Math.acos(1 - 2 * x / length);
- if (param == 0) {
- return radius * sqrt((theta - sin(2 * theta) / 2) / Math.PI);
+ if (MathUtil.equals(param, 0)) {
+ return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2) / Math.PI);
}
- return radius * sqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
+ return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
}
},
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationLaunchException;
import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
Configuration configuration = setupConfiguration(simulationConditions);
MotorInstanceConfiguration motorConfiguration = setupMotorConfiguration(configuration);
if (motorConfiguration.getMotorIDs().isEmpty()) {
- throw new SimulationLaunchException("No motors defined.");
+ throw new MotorIgnitionException("No motors defined in the simulation.");
}
// Initialize the simulation
flightData.addBranch(status.getFlightData());
- log.info("Warnings at the end of simulation: " + flightData.getWarningSet());
+ if (!flightData.getWarningSet().isEmpty()) {
+ log.info("Warnings at the end of simulation: " + flightData.getWarningSet());
+ }
// TODO: HIGH: Simulate branches
return flightData;
// If no motor has ignited, abort
if (!status.isMotorIgnited()) {
- throw new SimulationLaunchException("No motors ignited.");
+ throw new MotorIgnitionException("No motors ignited.");
}
return ret;
+++ /dev/null
-package net.sf.openrocket.simulation;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
-import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
-import net.sf.openrocket.masscalc.BasicMassCalculator;
-import net.sf.openrocket.models.atmosphere.AtmosphericModel;
-import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
-import net.sf.openrocket.models.gravity.BasicGravityModel;
-import net.sf.openrocket.models.wind.PinkNoiseWindModel;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.ChangeSource;
-import net.sf.openrocket.util.MathUtil;
-
-// TODO: HIGH: Move somewhere else and clean up
-@Deprecated
-public class GUISimulationConditions implements ChangeSource, Cloneable {
-
- public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
-
- /**
- * The ISA standard atmosphere.
- */
- private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
-
-
- private final Rocket rocket;
- private String motorID = null;
-
-
- /*
- * NOTE: When adding/modifying parameters, they must also be added to the
- * equals and copyFrom methods!!
- */
-
- // TODO: HIGH: Fetch default values from Prefs!
-
- private double launchRodLength = 1;
-
- /** Launch rod angle > 0, radians from vertical */
- private double launchRodAngle = 0;
-
- /** Launch rod direction, 0 = upwind, PI = downwind. */
- private double launchRodDirection = 0;
-
-
- private double windAverage = 2.0;
- private double windTurbulence = 0.1;
-
- private double launchAltitude = 0;
- private double launchLatitude = 45;
-
- private boolean useISA = true;
- private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
- private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
- private AtmosphericModel atmosphericModel = null;
-
-
- private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
- private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
-
- private boolean calculateExtras = true;
-
-
- private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
-
-
-
- public GUISimulationConditions(Rocket rocket) {
- this.rocket = rocket;
- }
-
-
-
- public Rocket getRocket() {
- return rocket;
- }
-
-
- public String getMotorConfigurationID() {
- return motorID;
- }
-
- /**
- * Set the motor configuration ID. This must be a valid motor configuration ID of
- * the rocket, otherwise the configuration is set to <code>null</code>.
- *
- * @param id the configuration to set.
- */
- public void setMotorConfigurationID(String id) {
- if (id != null)
- id = id.intern();
- if (!rocket.isMotorConfigurationID(id))
- id = null;
- if (id == motorID)
- return;
- motorID = id;
- fireChangeEvent();
- }
-
-
- public double getLaunchRodLength() {
- return launchRodLength;
- }
-
- public void setLaunchRodLength(double launchRodLength) {
- if (MathUtil.equals(this.launchRodLength, launchRodLength))
- return;
- this.launchRodLength = launchRodLength;
- fireChangeEvent();
- }
-
-
- public double getLaunchRodAngle() {
- return launchRodAngle;
- }
-
- public void setLaunchRodAngle(double launchRodAngle) {
- launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
- if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
- return;
- this.launchRodAngle = launchRodAngle;
- fireChangeEvent();
- }
-
-
- public double getLaunchRodDirection() {
- return launchRodDirection;
- }
-
- public void setLaunchRodDirection(double launchRodDirection) {
- launchRodDirection = MathUtil.reduce180(launchRodDirection);
- if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
- return;
- this.launchRodDirection = launchRodDirection;
- fireChangeEvent();
- }
-
-
-
- public double getWindSpeedAverage() {
- return windAverage;
- }
-
- public void setWindSpeedAverage(double windAverage) {
- if (MathUtil.equals(this.windAverage, windAverage))
- return;
- this.windAverage = MathUtil.max(windAverage, 0);
- fireChangeEvent();
- }
-
-
- public double getWindSpeedDeviation() {
- return windAverage * windTurbulence;
- }
-
- public void setWindSpeedDeviation(double windDeviation) {
- if (windAverage < 0.1) {
- windAverage = 0.1;
- }
- setWindTurbulenceIntensity(windDeviation / windAverage);
- }
-
-
- /**
- * Return the wind turbulence intensity (standard deviation / average).
- *
- * @return the turbulence intensity
- */
- public double getWindTurbulenceIntensity() {
- return windTurbulence;
- }
-
- /**
- * Set the wind standard deviation to match the given turbulence intensity.
- *
- * @param intensity the turbulence intensity
- */
- public void setWindTurbulenceIntensity(double intensity) {
- // Does not check equality so that setWindSpeedDeviation can be sure of event firing
- this.windTurbulence = intensity;
- fireChangeEvent();
- }
-
-
-
-
-
- public double getLaunchAltitude() {
- return launchAltitude;
- }
-
- public void setLaunchAltitude(double altitude) {
- if (MathUtil.equals(this.launchAltitude, altitude))
- return;
- this.launchAltitude = altitude;
- fireChangeEvent();
- }
-
-
- public double getLaunchLatitude() {
- return launchLatitude;
- }
-
- public void setLaunchLatitude(double launchLatitude) {
- launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
- if (MathUtil.equals(this.launchLatitude, launchLatitude))
- return;
- this.launchLatitude = launchLatitude;
- fireChangeEvent();
- }
-
-
-
-
-
- public boolean isISAAtmosphere() {
- return useISA;
- }
-
- public void setISAAtmosphere(boolean isa) {
- if (isa == useISA)
- return;
- useISA = isa;
- fireChangeEvent();
- }
-
-
- public double getLaunchTemperature() {
- return launchTemperature;
- }
-
-
-
- public void setLaunchTemperature(double launchTemperature) {
- if (MathUtil.equals(this.launchTemperature, launchTemperature))
- return;
- this.launchTemperature = launchTemperature;
- this.atmosphericModel = null;
- fireChangeEvent();
- }
-
-
-
- public double getLaunchPressure() {
- return launchPressure;
- }
-
-
-
- public void setLaunchPressure(double launchPressure) {
- if (MathUtil.equals(this.launchPressure, launchPressure))
- return;
- this.launchPressure = launchPressure;
- this.atmosphericModel = null;
- fireChangeEvent();
- }
-
-
- /**
- * Returns an atmospheric model corresponding to the launch conditions. The
- * atmospheric models may be shared between different calls.
- *
- * @return an AtmosphericModel object.
- */
- public AtmosphericModel getAtmosphericModel() {
- if (useISA) {
- return ISA_ATMOSPHERIC_MODEL;
- }
- if (atmosphericModel == null) {
- atmosphericModel = new ExtendedISAModel(launchAltitude,
- launchTemperature, launchPressure);
- }
- return atmosphericModel;
- }
-
-
- public double getTimeStep() {
- return timeStep;
- }
-
- public void setTimeStep(double timeStep) {
- if (MathUtil.equals(this.timeStep, timeStep))
- return;
- this.timeStep = timeStep;
- fireChangeEvent();
- }
-
- public double getMaximumStepAngle() {
- return maximumAngle;
- }
-
- public void setMaximumStepAngle(double maximumAngle) {
- maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
- if (MathUtil.equals(this.maximumAngle, maximumAngle))
- return;
- this.maximumAngle = maximumAngle;
- fireChangeEvent();
- }
-
-
-
- public boolean getCalculateExtras() {
- return calculateExtras;
- }
-
-
-
- public void setCalculateExtras(boolean calculateExtras) {
- if (this.calculateExtras == calculateExtras)
- return;
- this.calculateExtras = calculateExtras;
- fireChangeEvent();
- }
-
-
-
- @Override
- public GUISimulationConditions clone() {
- try {
- GUISimulationConditions copy = (GUISimulationConditions) super.clone();
- copy.listeners = new ArrayList<ChangeListener>();
- return copy;
- } catch (CloneNotSupportedException e) {
- throw new BugException(e);
- }
- }
-
-
- public void copyFrom(GUISimulationConditions src) {
-
- if (this.rocket == src.rocket) {
-
- this.motorID = src.motorID;
-
- } else {
-
- if (src.rocket.hasMotors(src.motorID)) {
- // Try to find a matching motor ID
- String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
- String matchID = null;
-
- for (String id : this.rocket.getMotorConfigurationIDs()) {
- if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
- matchID = id;
- break;
- }
- }
-
- this.motorID = matchID;
- } else {
- this.motorID = null;
- }
- }
-
- this.launchAltitude = src.launchAltitude;
- this.launchLatitude = src.launchLatitude;
- this.launchPressure = src.launchPressure;
- this.launchRodAngle = src.launchRodAngle;
- this.launchRodDirection = src.launchRodDirection;
- this.launchRodLength = src.launchRodLength;
- this.launchTemperature = src.launchTemperature;
- this.maximumAngle = src.maximumAngle;
- this.timeStep = src.timeStep;
- this.windAverage = src.windAverage;
- this.windTurbulence = src.windTurbulence;
- this.calculateExtras = src.calculateExtras;
-
- fireChangeEvent();
- }
-
-
-
- /**
- * Compares whether the two simulation conditions are equal. The two are considered
- * equal if the rocket, motor id and all variables are equal.
- */
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof GUISimulationConditions))
- return false;
- GUISimulationConditions o = (GUISimulationConditions) other;
- return ((this.rocket == o.rocket) &&
- this.motorID == o.motorID &&
- MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
- MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
- MathUtil.equals(this.launchPressure, o.launchPressure) &&
- MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
- MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
- MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
- MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
- MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
- MathUtil.equals(this.timeStep, o.timeStep) &&
- MathUtil.equals(this.windAverage, o.windAverage) &&
- MathUtil.equals(this.windTurbulence, o.windTurbulence) && this.calculateExtras == o.calculateExtras);
- }
-
- /**
- * Hashcode method compatible with {@link #equals(Object)}.
- */
- @Override
- public int hashCode() {
- if (motorID == null)
- return rocket.hashCode();
- return rocket.hashCode() + motorID.hashCode();
- }
-
- @Override
- public void addChangeListener(ChangeListener listener) {
- listeners.add(listener);
- }
-
- @Override
- public void removeChangeListener(ChangeListener listener) {
- listeners.remove(listener);
- }
-
- private final ChangeEvent event = new ChangeEvent(this);
-
- private void fireChangeEvent() {
- ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
-
- for (int i = array.length - 1; i >= 0; i--) {
- array[i].stateChanged(event);
- }
- }
-
-
- // TODO: HIGH: Clean up
- @Deprecated
- public SimulationConditions toSimulationConditions() {
- SimulationConditions conditions = new SimulationConditions();
-
- conditions.setRocket((Rocket) getRocket().copy());
- conditions.setMotorConfigurationID(getMotorConfigurationID());
- conditions.setLaunchRodLength(getLaunchRodLength());
- conditions.setLaunchRodAngle(getLaunchRodAngle());
- conditions.setLaunchRodDirection(getLaunchRodDirection());
- conditions.setLaunchAltitude(getLaunchAltitude());
- conditions.setLaunchLatitude(getLaunchLatitude());
-
- PinkNoiseWindModel windModel = new PinkNoiseWindModel();
- // TODO: HIGH: Randomness source for simulation
- windModel.setSeed(new Random().nextInt());
- windModel.setAverage(getWindSpeedAverage());
- windModel.setStandardDeviation(getWindSpeedDeviation());
- conditions.setWindModel(windModel);
-
- conditions.setAtmosphericModel(getAtmosphericModel());
-
- BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
- conditions.setGravityModel(gravityModel);
-
- conditions.setAerodynamicCalculator(new BarrowmanCalculator());
- conditions.setMassCalculator(new BasicMassCalculator());
-
- conditions.setTimeStep(getTimeStep());
- conditions.setMaximumAngleStep(getMaximumStepAngle());
-
- conditions.setCalculateExtras(getCalculateExtras());
-
- return conditions;
- }
-
-}
private static final LogHelper log = Application.getLogger();
+ /** Random value with which to XOR the random seed value */
+ private static final int SEED_RANDOMIZATION = 0x23E3A01F;
+
/**
* A recommended reasonably accurate time step.
private static final double MIN_TIME_STEP = 0.001;
- // TODO: HIGH: Randomness source from simulation
- private final Random random = new Random();
+ private Random random;
@Override
public RK4SimulationStatus initialize(SimulationStatus original) {
- log.info("Performing RK4SimulationStepper initialization");
-
RK4SimulationStatus status = new RK4SimulationStatus();
status.copyFrom(original);
Math.cos(sim.getLaunchRodAngle())
));
+ this.random = new Random(original.getSimulationConditions().getRandomSeed() ^ SEED_RANDOMIZATION);
+
return status;
}
private List<SimulationListener> simulationListeners = new ArrayList<SimulationListener>();
+ private int randomSeed = 0;
+
private int modID = 0;
private int modIDadd = 0;
-
public AerodynamicCalculator getAerodynamicCalculator() {
return aerodynamicCalculator;
}
}
+
+ public int getRandomSeed() {
+ return randomSeed;
+ }
+
+
+ public void setRandomSeed(int randomSeed) {
+ this.randomSeed = randomSeed;
+ this.modID++;
+ }
+
+
// TODO: HIGH: Make cleaner
public List<SimulationListener> getSimulationListenerList() {
return simulationListeners;
--- /dev/null
+package net.sf.openrocket.simulation;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.masscalc.BasicMassCalculator;
+import net.sf.openrocket.models.atmosphere.AtmosphericModel;
+import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
+import net.sf.openrocket.models.gravity.BasicGravityModel;
+import net.sf.openrocket.models.wind.PinkNoiseWindModel;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * A class holding simulation options in basic parameter form and which functions
+ * as a ChangeSource. A SimulationConditions instance is generated from this class
+ * using {@link #toSimulationConditions()}.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SimulationOptions implements ChangeSource, Cloneable {
+
+ public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
+
+ /**
+ * The ISA standard atmosphere.
+ */
+ private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
+
+
+ private final Rocket rocket;
+ private String motorID = null;
+
+
+ /*
+ * NOTE: When adding/modifying parameters, they must also be added to the
+ * equals and copyFrom methods!!
+ */
+
+ // TODO: HIGH: Fetch default values from Prefs!
+
+ private double launchRodLength = 1;
+
+ /** Launch rod angle > 0, radians from vertical */
+ private double launchRodAngle = 0;
+
+ /** Launch rod direction, 0 = upwind, PI = downwind. */
+ private double launchRodDirection = 0;
+
+
+ private double windAverage = 2.0;
+ private double windTurbulence = 0.1;
+
+ private double launchAltitude = 0;
+ private double launchLatitude = 45;
+
+ private boolean useISA = true;
+ private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
+ private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
+
+
+ private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
+ private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
+
+ private int randomSeed = new Random().nextInt();
+
+ private boolean calculateExtras = true;
+
+
+ private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
+
+
+
+ public SimulationOptions(Rocket rocket) {
+ this.rocket = rocket;
+ }
+
+
+
+ public Rocket getRocket() {
+ return rocket;
+ }
+
+
+ public String getMotorConfigurationID() {
+ return motorID;
+ }
+
+ /**
+ * Set the motor configuration ID. This must be a valid motor configuration ID of
+ * the rocket, otherwise the configuration is set to <code>null</code>.
+ *
+ * @param id the configuration to set.
+ */
+ public void setMotorConfigurationID(String id) {
+ if (id != null)
+ id = id.intern();
+ if (!rocket.isMotorConfigurationID(id))
+ id = null;
+ if (id == motorID)
+ return;
+ motorID = id;
+ fireChangeEvent();
+ }
+
+
+ public double getLaunchRodLength() {
+ return launchRodLength;
+ }
+
+ public void setLaunchRodLength(double launchRodLength) {
+ if (MathUtil.equals(this.launchRodLength, launchRodLength))
+ return;
+ this.launchRodLength = launchRodLength;
+ fireChangeEvent();
+ }
+
+
+ public double getLaunchRodAngle() {
+ return launchRodAngle;
+ }
+
+ public void setLaunchRodAngle(double launchRodAngle) {
+ launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
+ if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
+ return;
+ this.launchRodAngle = launchRodAngle;
+ fireChangeEvent();
+ }
+
+
+ public double getLaunchRodDirection() {
+ return launchRodDirection;
+ }
+
+ public void setLaunchRodDirection(double launchRodDirection) {
+ launchRodDirection = MathUtil.reduce180(launchRodDirection);
+ if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
+ return;
+ this.launchRodDirection = launchRodDirection;
+ fireChangeEvent();
+ }
+
+
+
+ public double getWindSpeedAverage() {
+ return windAverage;
+ }
+
+ public void setWindSpeedAverage(double windAverage) {
+ if (MathUtil.equals(this.windAverage, windAverage))
+ return;
+ this.windAverage = MathUtil.max(windAverage, 0);
+ fireChangeEvent();
+ }
+
+
+ public double getWindSpeedDeviation() {
+ return windAverage * windTurbulence;
+ }
+
+ public void setWindSpeedDeviation(double windDeviation) {
+ if (windAverage < 0.1) {
+ windAverage = 0.1;
+ }
+ setWindTurbulenceIntensity(windDeviation / windAverage);
+ }
+
+
+ /**
+ * Return the wind turbulence intensity (standard deviation / average).
+ *
+ * @return the turbulence intensity
+ */
+ public double getWindTurbulenceIntensity() {
+ return windTurbulence;
+ }
+
+ /**
+ * Set the wind standard deviation to match the given turbulence intensity.
+ *
+ * @param intensity the turbulence intensity
+ */
+ public void setWindTurbulenceIntensity(double intensity) {
+ // Does not check equality so that setWindSpeedDeviation can be sure of event firing
+ this.windTurbulence = intensity;
+ fireChangeEvent();
+ }
+
+
+
+
+
+ public double getLaunchAltitude() {
+ return launchAltitude;
+ }
+
+ public void setLaunchAltitude(double altitude) {
+ if (MathUtil.equals(this.launchAltitude, altitude))
+ return;
+ this.launchAltitude = altitude;
+ fireChangeEvent();
+ }
+
+
+ public double getLaunchLatitude() {
+ return launchLatitude;
+ }
+
+ public void setLaunchLatitude(double launchLatitude) {
+ launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
+ if (MathUtil.equals(this.launchLatitude, launchLatitude))
+ return;
+ this.launchLatitude = launchLatitude;
+ fireChangeEvent();
+ }
+
+
+
+
+
+ public boolean isISAAtmosphere() {
+ return useISA;
+ }
+
+ public void setISAAtmosphere(boolean isa) {
+ if (isa == useISA)
+ return;
+ useISA = isa;
+ fireChangeEvent();
+ }
+
+
+ public double getLaunchTemperature() {
+ return launchTemperature;
+ }
+
+
+
+ public void setLaunchTemperature(double launchTemperature) {
+ if (MathUtil.equals(this.launchTemperature, launchTemperature))
+ return;
+ this.launchTemperature = launchTemperature;
+ fireChangeEvent();
+ }
+
+
+
+ public double getLaunchPressure() {
+ return launchPressure;
+ }
+
+
+
+ public void setLaunchPressure(double launchPressure) {
+ if (MathUtil.equals(this.launchPressure, launchPressure))
+ return;
+ this.launchPressure = launchPressure;
+ fireChangeEvent();
+ }
+
+
+ /**
+ * Returns an atmospheric model corresponding to the launch conditions. The
+ * atmospheric models may be shared between different calls.
+ *
+ * @return an AtmosphericModel object.
+ */
+ private AtmosphericModel getAtmosphericModel() {
+ if (useISA) {
+ return ISA_ATMOSPHERIC_MODEL;
+ }
+ return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
+ }
+
+
+ public double getTimeStep() {
+ return timeStep;
+ }
+
+ public void setTimeStep(double timeStep) {
+ if (MathUtil.equals(this.timeStep, timeStep))
+ return;
+ this.timeStep = timeStep;
+ fireChangeEvent();
+ }
+
+ public double getMaximumStepAngle() {
+ return maximumAngle;
+ }
+
+ public void setMaximumStepAngle(double maximumAngle) {
+ maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
+ if (MathUtil.equals(this.maximumAngle, maximumAngle))
+ return;
+ this.maximumAngle = maximumAngle;
+ fireChangeEvent();
+ }
+
+
+
+ public boolean getCalculateExtras() {
+ return calculateExtras;
+ }
+
+
+
+ public void setCalculateExtras(boolean calculateExtras) {
+ if (this.calculateExtras == calculateExtras)
+ return;
+ this.calculateExtras = calculateExtras;
+ fireChangeEvent();
+ }
+
+
+
+ public int getRandomSeed() {
+ return randomSeed;
+ }
+
+ public void setRandomSeed(int randomSeed) {
+ if (this.randomSeed == randomSeed) {
+ return;
+ }
+ this.randomSeed = randomSeed;
+ /*
+ * This does not fire an event since we don't want to invalidate simulation results
+ * due to changing the seed value. This needs to be revisited if the user is ever
+ * allowed to select the seed value.
+ */
+ // fireChangeEvent();
+ }
+
+ /**
+ * Randomize the random seed value.
+ */
+ public void randomizeSeed() {
+ this.randomSeed = new Random().nextInt();
+ // fireChangeEvent();
+ }
+
+
+
+ @Override
+ public SimulationOptions clone() {
+ try {
+ SimulationOptions copy = (SimulationOptions) super.clone();
+ copy.listeners = new ArrayList<ChangeListener>();
+ return copy;
+ } catch (CloneNotSupportedException e) {
+ throw new BugException(e);
+ }
+ }
+
+
+ public void copyFrom(SimulationOptions src) {
+
+ if (this.rocket == src.rocket) {
+
+ this.motorID = src.motorID;
+
+ } else {
+
+ if (src.rocket.hasMotors(src.motorID)) {
+ // Try to find a matching motor ID
+ String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
+ String matchID = null;
+
+ for (String id : this.rocket.getMotorConfigurationIDs()) {
+ if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
+ matchID = id;
+ break;
+ }
+ }
+
+ this.motorID = matchID;
+ } else {
+ this.motorID = null;
+ }
+ }
+
+ this.launchAltitude = src.launchAltitude;
+ this.launchLatitude = src.launchLatitude;
+ this.launchPressure = src.launchPressure;
+ this.launchRodAngle = src.launchRodAngle;
+ this.launchRodDirection = src.launchRodDirection;
+ this.launchRodLength = src.launchRodLength;
+ this.launchTemperature = src.launchTemperature;
+ this.maximumAngle = src.maximumAngle;
+ this.timeStep = src.timeStep;
+ this.windAverage = src.windAverage;
+ this.windTurbulence = src.windTurbulence;
+ this.calculateExtras = src.calculateExtras;
+ this.randomSeed = src.randomSeed;
+
+ fireChangeEvent();
+ }
+
+
+
+ /**
+ * Compares whether the two simulation conditions are equal. The two are considered
+ * equal if the rocket, motor id and all variables are equal.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof SimulationOptions))
+ return false;
+ SimulationOptions o = (SimulationOptions) other;
+ return ((this.rocket == o.rocket) &&
+ this.motorID.equals(o.motorID) &&
+ MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
+ MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
+ MathUtil.equals(this.launchPressure, o.launchPressure) &&
+ MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
+ MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
+ MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
+ MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
+ MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
+ MathUtil.equals(this.timeStep, o.timeStep) &&
+ MathUtil.equals(this.windAverage, o.windAverage) &&
+ MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
+ this.calculateExtras == o.calculateExtras && this.randomSeed == o.randomSeed);
+ }
+
+ /**
+ * Hashcode method compatible with {@link #equals(Object)}.
+ */
+ @Override
+ public int hashCode() {
+ if (motorID == null)
+ return rocket.hashCode();
+ return rocket.hashCode() + motorID.hashCode();
+ }
+
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ private final ChangeEvent event = new ChangeEvent(this);
+
+ private void fireChangeEvent() {
+ ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
+
+ for (int i = array.length - 1; i >= 0; i--) {
+ array[i].stateChanged(event);
+ }
+ }
+
+
+ // TODO: HIGH: Clean up
+ public SimulationConditions toSimulationConditions() {
+ SimulationConditions conditions = new SimulationConditions();
+
+ conditions.setRocket((Rocket) getRocket().copy());
+ conditions.setMotorConfigurationID(getMotorConfigurationID());
+ conditions.setLaunchRodLength(getLaunchRodLength());
+ conditions.setLaunchRodAngle(getLaunchRodAngle());
+ conditions.setLaunchRodDirection(getLaunchRodDirection());
+ conditions.setLaunchAltitude(getLaunchAltitude());
+ conditions.setLaunchLatitude(getLaunchLatitude());
+ conditions.setRandomSeed(randomSeed);
+
+ PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
+ windModel.setAverage(getWindSpeedAverage());
+ windModel.setStandardDeviation(getWindSpeedDeviation());
+ conditions.setWindModel(windModel);
+
+ conditions.setAtmosphericModel(getAtmosphericModel());
+
+ BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
+ conditions.setGravityModel(gravityModel);
+
+ conditions.setAerodynamicCalculator(new BarrowmanCalculator());
+ conditions.setMassCalculator(new BasicMassCalculator());
+
+ conditions.setTimeStep(getTimeStep());
+ conditions.setMaximumAngleStep(getMaximumStepAngle());
+
+ conditions.setCalculateExtras(getCalculateExtras());
+
+ return conditions;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.simulation.exception;
+
+/**
+ * An exception signifying that the simulation failed because no motors were
+ * defined or ignited in the rocket.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MotorIgnitionException extends SimulationLaunchException {
+
+ public MotorIgnitionException(String message) {
+ super(message);
+ }
+
+ public MotorIgnitionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class SimulationLaunchException extends SimulationException {
-
- public SimulationLaunchException() {
-
- }
-
+
public SimulationLaunchException(String message) {
super(message);
}
-
- public SimulationLaunchException(Throwable cause) {
- super(cause);
- }
-
+
public SimulationLaunchException(String message, Throwable cause) {
super(message, cause);
}
-
+
}
* @return a translator.
*/
public static Translator getTranslator() {
- if (baseTranslator instanceof DebugTranslator) {
- return baseTranslator;
- }
-
Translator t = baseTranslator;
t = new ClassBasedTranslator(t, 1);
t = new ExceptionSuppressingTranslator(t);
package net.sf.openrocket.unit;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Iterator;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.MathUtil;
private final Configuration configuration;
private final Rocket rocket;
- private double caliber = -1;
+ private int rocketModId = -1;
+ private int configurationModId = -1;
-
- /* Listener for rocket and configuration, resets the caliber to -1. */
- private final ChangeListener listener = new ChangeListener() {
- @Override
- public void stateChanged(ChangeEvent e) {
- caliber = -1;
- }
- };
+ private double caliber = -1;
+
public CaliberUnit(Configuration configuration) {
super(1.0, "cal");
this.configuration = configuration;
this.rocket = null;
} else {
this.rocket = configuration.getRocket();
- configuration.addChangeListener(listener);
}
}
super(1.0, "cal");
this.configuration = null;
this.rocket = rocket;
- if (rocket != null) {
- rocket.addChangeListener(listener);
+ }
+
+ public CaliberUnit(double reference) {
+ super(1.0, "cal");
+ this.configuration = null;
+ this.rocket = null;
+ this.caliber = reference;
+
+ if (reference <= 0) {
+ throw new IllegalArgumentException("Illegal reference = " + reference);
}
}
@Override
public double fromUnit(double value) {
- if (caliber < 0)
- calculateCaliber();
+ checkCaliber();
return value * caliber;
}
@Override
public double toUnit(double value) {
- if (caliber < 0)
- calculateCaliber();
+ checkCaliber();
return value / caliber;
}
- // TODO: HIGH: Check caliber calculation method...
- private void calculateCaliber() {
- caliber = 0;
-
- Iterator<RocketComponent> iterator;
- if (configuration != null) {
- iterator = configuration.iterator();
- } else if (rocket != null) {
- iterator = rocket.iterator(false);
- } else {
- Collection<RocketComponent> set = Collections.emptyList();
- iterator = set.iterator();
+
+ private void checkCaliber() {
+ if (configuration != null && configuration.getModID() != configurationModId) {
+ caliber = -1;
+ configurationModId = configuration.getModID();
+ }
+ if (rocket != null && rocket.getModID() != rocketModId) {
+ caliber = -1;
+ rocketModId = rocket.getModID();
+ }
+ if (caliber < 0) {
+ if (configuration != null) {
+ caliber = calculateCaliber(configuration);
+ } else if (rocket != null) {
+ caliber = calculateCaliber(rocket);
+ } else {
+ throw new BugException("Both rocket and configuration are null");
+ }
}
+ }
+
+
+ /**
+ * Calculate the caliber of a rocket configuration.
+ *
+ * @param config the rocket configuration
+ * @return the caliber of the rocket, or the default caliber.
+ */
+ public static double calculateCaliber(Configuration config) {
+ return calculateCaliber(config.iterator());
+ }
+
+ /**
+ * Calculate the caliber of a rocket.
+ *
+ * @param rocket the rocket
+ * @return the caliber of the rocket, or the default caliber.
+ */
+ public static double calculateCaliber(Rocket rocket) {
+ return calculateCaliber(rocket.iterator());
+ }
+
+
+
+ private static double calculateCaliber(Iterator<RocketComponent> iterator) {
+ double cal = 0;
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
if (c instanceof SymmetricComponent) {
double r1 = ((SymmetricComponent) c).getForeRadius() * 2;
double r2 = ((SymmetricComponent) c).getAftRadius() * 2;
- caliber = MathUtil.max(caliber, r1, r2);
+ cal = MathUtil.max(cal, r1, r2);
}
}
- if (caliber <= 0)
- caliber = DEFAULT_CALIBER;
+ if (cal < 0.0001)
+ cal = DEFAULT_CALIBER;
+
+ return cal;
}
}
public static final UnitGroup UNITS_AREA;
public static final UnitGroup UNITS_STABILITY;
+ /**
+ * This unit group contains only the caliber unit that never scales the originating "SI" value.
+ * It can be used in cases where the originating value is already in calibers to obtains the correct unit.
+ */
+ public static final UnitGroup UNITS_STABILITY_CALIBERS;
public static final UnitGroup UNITS_VELOCITY;
public static final UnitGroup UNITS_ACCELERATION;
public static final UnitGroup UNITS_MASS;
UNITS_STABILITY.addUnit(new CaliberUnit((Rocket) null));
UNITS_STABILITY.setDefaultUnit(3);
+ UNITS_STABILITY_CALIBERS = new UnitGroup();
+ UNITS_STABILITY_CALIBERS.addUnit(new GeneralUnit(1, "cal"));
+
+
UNITS_VELOCITY = new UnitGroup();
UNITS_VELOCITY.addUnit(new GeneralUnit(1, "m/s"));
UNITS_VELOCITY.addUnit(new GeneralUnit(1 / 3.6, "km/h"));
UNITS_RELATIVE = new UnitGroup();
UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0));
- UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01));
+ UNITS_RELATIVE.addUnit(new GeneralUnit(0.01, "%"));
UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001));
+ // UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0));
+ // UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01));
+ // UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001));
UNITS_RELATIVE.setDefaultUnit(1);
}
-
+ /**
+ * Return a UnitGroup for stability units based on the rocket.
+ *
+ * @param rocket the rocket from which to calculate the caliber
+ * @return the unit group
+ */
public static UnitGroup stabilityUnits(Rocket rocket) {
return new StabilityUnitGroup(rocket);
}
+ /**
+ * Return a UnitGroup for stability units based on the rocket configuration.
+ *
+ * @param config the rocket configuration from which to calculate the caliber
+ * @return the unit group
+ */
public static UnitGroup stabilityUnits(Configuration config) {
return new StabilityUnitGroup(config);
}
+ /**
+ * Return a UnitGroup for stability units based on a constant caliber.
+ *
+ * @param reference the constant reference length
+ * @return the unit group
+ */
+ public static UnitGroup stabilityUnits(double reference) {
+ return new StabilityUnitGroup(reference);
+ }
+
+
//////////////////////////////////////////////////////
- private ArrayList<Unit> units = new ArrayList<Unit>();
- private int defaultUnit = 0;
+ protected ArrayList<Unit> units = new ArrayList<Unit>();
+ protected int defaultUnit = 0;
public int getUnitCount() {
return units.size();
return units.indexOf(u);
}
- public void addUnit(Unit u) {
+ private void addUnit(Unit u) {
units.add(u);
}
- public void addUnit(int n, Unit u) {
- units.add(n, u);
- }
-
- public void removeUnit(int n) {
- units.remove(n);
- }
-
public boolean contains(Unit u) {
return units.contains(u);
}
*/
private static class StabilityUnitGroup extends UnitGroup {
- private final CaliberUnit caliberUnit;
-
+ public StabilityUnitGroup(double ref) {
+ this(new CaliberUnit(ref));
+ }
public StabilityUnitGroup(Rocket rocket) {
- caliberUnit = new CaliberUnit(rocket);
+ this(new CaliberUnit(rocket));
}
public StabilityUnitGroup(Configuration config) {
- caliberUnit = new CaliberUnit(config);
- }
-
-
- //// Modify CaliberUnit to use local variable
-
- @Override
- public Unit getDefaultUnit() {
- return getUnit(UNITS_STABILITY.getDefaultUnitIndex());
+ this(new CaliberUnit(config));
}
- @Override
- public Unit getUnit(int n) {
- Unit u = UNITS_STABILITY.getUnit(n);
- if (u instanceof CaliberUnit) {
- return caliberUnit;
- }
- return u;
- }
-
- @Override
- public int getUnitIndex(Unit u) {
- if (u instanceof CaliberUnit) {
- for (int i = 0; i < UNITS_STABILITY.getUnitCount(); i++) {
- if (UNITS_STABILITY.getUnit(i) instanceof CaliberUnit)
- return i;
+ private StabilityUnitGroup(CaliberUnit caliberUnit) {
+ this.units.addAll(UnitGroup.UNITS_STABILITY.units);
+ this.defaultUnit = UnitGroup.UNITS_STABILITY.defaultUnit;
+ for (int i = 0; i < units.size(); i++) {
+ if (units.get(i) instanceof CaliberUnit) {
+ units.set(i, caliberUnit);
}
}
- return UNITS_STABILITY.getUnitIndex(u);
}
-
- //// Pass on to UNITS_STABILITY
-
- @Override
- public int getDefaultUnitIndex() {
- return UNITS_STABILITY.getDefaultUnitIndex();
- }
-
@Override
public void setDefaultUnit(int n) {
+ super.setDefaultUnit(n);
UNITS_STABILITY.setDefaultUnit(n);
}
-
- @Override
- public int getUnitCount() {
- return UNITS_STABILITY.getUnitCount();
- }
-
-
- //// Unsupported methods
-
- @Override
- public void addUnit(int n, Unit u) {
- throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
- }
-
- @Override
- public void addUnit(Unit u) {
- throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
- }
-
- @Override
- public void removeUnit(int n) {
- throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
- }
}
}
import net.sf.openrocket.util.MathUtil;
/**
- * A class representing an SI value and a unit. The toString() method yields the
+ * An immutable class representing an SI value and a unit. The toString() method yields the
* current value in the current units. This class may be used to encapsulate
* a sortable value for example for tables. The sorting is performed by the
* value in the current units, ignoring the unit.
*/
public class Value implements Comparable<Value> {
- private double value;
- private Unit unit;
+ private final double value;
+ private final Unit unit;
/**
/**
- * Get the value of this object.
+ * Get the value of this object (in SI units).
*
* @return the value
*/
return value;
}
- /**
- * Set the value of this object.
- *
- * @param value the value to set
- */
- public void setValue(double value) {
- this.value = value;
- }
-
+
/**
* Get the value of this object in the current units.
*
}
- /**
- * Set the value of this object in the current units.
- *
- * @param value the value in current units.
- */
- public void setUnitValue(double value) {
- this.value = unit.fromUnit(value);
- }
-
-
/**
* Get the unit of this object.
*
return unit;
}
- /**
- * Set the value of this object.
- *
- * @param unit the unit to set (<code>null</code> not allowed)
- */
- public void setUnit(Unit unit) {
- if (unit == null) {
- throw new IllegalArgumentException("unit is null");
- }
- this.unit = unit;
- }
-
/**
* Return a string formatted using the {@link Unit#toStringUnit(double)} method
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class Chars {
-
+
/** The fraction 1/2 */
public static final char FRAC12 = '\u00BD';
/** The fraction 1/4 */
/** Micro sign (Greek letter mu) */
public static final char MICRO = '\u00B5';
-
+
/** Alpha */
public static final char ALPHA = '\u03b1';
/** Theta */
public static final char COPY = '\u00A9';
/** A centered bullet */
public static final char BULLET = '\u2022';
+
+ /** Left arrow (light) */
+ public static final char LEFT_ARROW = '\u2190';
+ /** Right arrow (light) */
+ public static final char RIGHT_ARROW = '\u2192';
+
}
*/
public double length() {
if (length < 0) {
- length = Math.sqrt(x * x + y * y + z * z);
+ length = MathUtil.safeSqrt(x * x + y * y + z * z);
}
return length;
}
private static final Translator trans = Application.getTranslator();
- // TODO: MEDIUM: Rename translation keys
+ // TODO: HIGH: Rename translation keys
/** File filter for any rocket designs (*.ork, *.rkt) */
public static final FileFilter ALL_DESIGNS_FILTER =
public static final FileFilter PDF_FILTER =
new SimpleFileFilter(trans.get("filetypes.pdf"), ".pdf");
+ /** File filter for CSV files (*.csv) */
+ public static final FileFilter CSV_FILE_FILTER =
+ new SimpleFileFilter(trans.get("SimExpPan.desc"), ".csv");
+
+
private FileHelper() {
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
+import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
}
+ /**
+ * Changes the style of the font of the specified border.
+ *
+ * @param border the component for which to change the font
+ * @param style the change in the font style
+ */
+ public static void changeFontStyle(TitledBorder border, int style) {
+ /*
+ * There's been an NPE caused by the font changing, this is debug for it.
+ */
+ if (border == null) {
+ throw new BugException("border is null");
+ }
+ Font font = border.getTitleFont();
+ if (font == null) {
+ throw new BugException("Border font is null");
+ }
+ font = font.deriveFont(style);
+ if (font == null) {
+ throw new BugException("Derived font is null");
+ }
+ border.setTitleFont(font);
+ }
+
+
+
/**
* Traverses recursively the component tree, and sets all applicable component
* models to null, so as to remove the listener connections. After calling this
import java.util.Comparator;
import java.util.List;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
public class MathUtil {
+ private static final LogHelper log = Application.getLogger();
+
public static final double EPSILON = 0.00000001; // 10mm^3 in m^3
/**
}
+ /**
+ * Return the square root of a value. If the value is negative, zero is returned.
+ * This is safer in cases where rounding errors might make a value slightly negative.
+ *
+ * @param d the value of which the square root is to be taken.
+ * @return the square root of the value.
+ */
+ public static double safeSqrt(double d) {
+ if (d < 0) {
+ if (d < 0.01) {
+ log.warn(1, "Attempting to compute sqrt(" + d + ")");
+ }
+ return 0;
+ }
+ return Math.sqrt(d);
+ }
+
+
+
public static boolean equals(double a, double b) {
double absb = Math.abs(b);
+++ /dev/null
-package net.sf.openrocket.util;
-
-import java.io.Serializable;
-
-/**
- * An immutable class of weighted coordinates. The weights are non-negative.
- *
- * Can also be used as non-weighted coordinates with weight=0.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public final class MutableCoordinate implements Serializable {
- public static final MutableCoordinate NUL = new MutableCoordinate(0,0,0,0);
- public static final MutableCoordinate NaN = new MutableCoordinate(Double.NaN,Double.NaN,
- Double.NaN,Double.NaN);
- public static final double COMPARISON_DELTA = 0.000001;
- private double x,y,z;
- private double weight;
-
-
- /* Count and report the number of times a Coordinate is constructed: */
-// private static int count=0;
-// {
-// count++;
-// if ((count % 1000) == 0) {
-// System.out.println("Coordinate instantiated "+count+" times");
-// }
-// }
-
-
- public MutableCoordinate() {
- x=0;
- y=0;
- z=0;
- weight=0;
- }
-
- public MutableCoordinate(double x) {
- this.x = x;
- this.y = 0;
- this.z = 0;
- weight = 0;
- }
-
- public MutableCoordinate(double x, double y) {
- this.x = x;
- this.y = y;
- this.z = 0;
- weight = 0;
- }
-
- public MutableCoordinate(double x, double y, double z) {
- this.x = x;
- this.y = y;
- this.z = z;
- weight = 0;
- }
- public MutableCoordinate(double x, double y, double z, double w) {
- this.x = x;
- this.y = y;
- this.z = z;
- this.weight=w;
- }
-
-
- public boolean isWeighted() {
- return (weight != 0);
- }
-
- public boolean isNaN() {
- return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z) || Double.isNaN(weight);
- }
-
- public MutableCoordinate setX(double x) {
- return new MutableCoordinate(x,this.y,this.z,this.weight);
- }
-
- public MutableCoordinate setY(double y) {
- return new MutableCoordinate(this.x,y,this.z,this.weight);
- }
-
- public MutableCoordinate setZ(double z) {
- return new MutableCoordinate(this.x,this.y,z,this.weight);
- }
-
- public MutableCoordinate setWeight(double weight) {
- return new MutableCoordinate(this.x, this.y, this.z, weight);
- }
-
- public MutableCoordinate setXYZ(MutableCoordinate c) {
- return new MutableCoordinate(c.x, c.y, c.z, this.weight);
- }
-
- public double getX() {
- return x;
- }
- public double getY() {
- return y;
- }
- public double getZ() {
- return z;
- }
-
-
- /**
- * Add the coordinate and weight of two coordinates.
- *
- * @param other the other <code>Coordinate</code>
- * @return the sum of the coordinates
- */
- public MutableCoordinate add(MutableCoordinate other) {
- this.x += other.x;
- this.y += other.y;
- this.z += other.z;
- this.weight += other.weight;
- return this;
- }
-
- public MutableCoordinate add(double x, double y, double z) {
- this.x += x;
- this.y += y;
- this.z += z;
- return this;
- }
-
- public MutableCoordinate add(double x, double y, double z, double weight) {
- return new MutableCoordinate(this.x+x, this.y+y, this.z+z, this.weight+weight);
- }
-
- /**
- * Subtract a Coordinate from this Coordinate. The weight of the resulting Coordinate
- * is the same as of this Coordinate, the weight of the argument is ignored.
- *
- * @param other Coordinate to subtract from this.
- * @return The result
- */
- public MutableCoordinate sub(MutableCoordinate other) {
- return new MutableCoordinate(this.x-other.x, this.y-other.y, this.z-other.z, this.weight);
- }
-
- /**
- * Subtract the specified values from this Coordinate. The weight of the result
- * is the same as the weight of this Coordinate.
- *
- * @param x x value to subtract
- * @param y y value to subtract
- * @param z z value to subtract
- * @return the result.
- */
- public MutableCoordinate sub(double x, double y, double z) {
- return new MutableCoordinate(this.x - x, this.y - y, this.z - z, this.weight);
- }
-
-
- /**
- * Multiply the <code>Coordinate</code> with a scalar. All coordinates and the
- * weight are multiplied by the given scalar.
-
- * @param m Factor to multiply by.
- * @return The product.
- */
- public MutableCoordinate multiply(double m) {
- return new MutableCoordinate(this.x*m, this.y*m, this.z*m, this.weight*m);
- }
-
- /**
- * Dot product of two Coordinates, taken as vectors. Equal to
- * x1*x2+y1*y2+z1*z2
- * @param other Coordinate to take product with.
- * @return The dot product.
- */
- public double dot(MutableCoordinate other) {
- return this.x*other.x + this.y*other.y + this.z*other.z;
- }
- /**
- * Dot product of two Coordinates.
- */
- public static double dot(MutableCoordinate v1, MutableCoordinate v2) {
- return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
- }
-
- /**
- * Distance from the origin to the Coordinate.
- */
- public double length() {
- return Math.sqrt(x*x+y*y+z*z);
- }
-
- /**
- * Square of the distance from the origin to the Coordinate.
- */
- public double length2() {
- return x*x+y*y+z*z;
- }
-
- /**
- * Returns a new coordinate which has the same direction from the origin as this
- * coordinate but is at a distance of one. If this coordinate is the origin,
- * this method throws an <code>IllegalStateException</code>. The weight of the
- * coordinate is unchanged.
- *
- * @return the coordinate normalized to distance one of the origin.
- * @throws IllegalStateException if this coordinate is the origin.
- */
- public MutableCoordinate normalize() {
- double l = length();
- if (l < 0.0000001) {
- throw new IllegalStateException("Cannot normalize zero coordinate");
- }
- return new MutableCoordinate(x/l, y/l, z/l, weight);
- }
-
-
-
-
- /**
- * Weighted average of two coordinates. If either of the weights are positive,
- * the result is the weighted average of the coordinates and the weight is the sum
- * of the original weights. If the sum of the weights is zero (and especially if
- * both of the weights are zero), the result is the unweighted average of the
- * coordinates with weight zero.
- * <p>
- * If <code>other</code> is <code>null</code> then this <code>Coordinate</code> is
- * returned.
- */
- public MutableCoordinate average(MutableCoordinate other) {
- double x,y,z,w;
-
- if (other == null)
- return this;
-
- w = this.weight + other.weight;
- if (MathUtil.equals(w, 0)) {
- x = (this.x+other.x)/2;
- y = (this.y+other.y)/2;
- z = (this.z+other.z)/2;
- w = 0;
- } else {
- x = (this.x*this.weight + other.x*other.weight)/w;
- y = (this.y*this.weight + other.y*other.weight)/w;
- z = (this.z*this.weight + other.z*other.weight)/w;
- }
- return new MutableCoordinate(x,y,z,w);
- }
-
-
- /**
- * Tests whether the coordinates (not weight!) are the same.
- *
- * Compares only the (x,y,z) coordinates, NOT the weight. Coordinate comparison is
- * done to the precision of COMPARISON_DELTA.
- *
- * @param other Coordinate to compare to.
- * @return true if the coordinates are equal
- */
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof MutableCoordinate))
- return false;
-
- final MutableCoordinate c = (MutableCoordinate)other;
- return (MathUtil.equals(this.x, c.x) &&
- MathUtil.equals(this.y, c.y) &&
- MathUtil.equals(this.z, c.z));
- }
-
- /**
- * Hash code method compatible with {@link #equals(Object)}.
- */
- @Override
- public int hashCode() {
- return (int)((x+y+z)*100000);
- }
-
-
- @Override
- public String toString() {
- if (isWeighted())
- return String.format("(%.3f,%.3f,%.3f,w=%.3f)", x,y,z,weight);
- else
- return String.format("(%.3f,%.3f,%.3f)", x,y,z);
- }
-
-
-
- public static void main(String[] arg) {
- double a=1.2;
- double x;
- MutableCoordinate c;
- long t1, t2;
-
- x = 0;
- t1 = System.nanoTime();
- for (int i=0; i < 100000000; i++) {
- x = x + a;
- }
- t2 = System.nanoTime();
- System.out.println("Value: "+x);
- System.out.println("Plain addition: "+ ((t2-t1+500000)/1000000) + " ms");
-
- c = MutableCoordinate.NUL;
- t1 = System.nanoTime();
- for (int i=0; i < 100000000; i++) {
- c = c.add(a,0,0);
- }
- t2 = System.nanoTime();
- System.out.println("Value: "+c.x);
- System.out.println("Coordinate addition: "+ ((t2-t1+500000)/1000000) + " ms");
-
- }
-
-}
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
public static Simulation getBackgroundSimulation(Rocket rocket) {
Simulation s = new Simulation(rocket);
- GUISimulationConditions cond = s.getConditions();
+ SimulationOptions cond = s.getOptions();
cond.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP * 2);
cond.setWindSpeedAverage(1.0);
*/
public double norm() {
if (norm < 0) {
- norm = Math.sqrt(x * x + y * y + z * z + w * w);
+ norm = MathUtil.safeSqrt(x * x + y * y + z * z + w * w);
}
return norm;
}
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.Pair;
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = -0.43;
- final double dref = 0.33;
+ final Value dref = new Value(ddist, Unit.NOUNIT2);
final double pvalue = 9.81;
+ final Value pvalueValue = new Value(9.81, Unit.NOUNIT2);
final double gvalue = 8.81;
final Point point = new Point(p1, p2);
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
- oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+ oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
+ oneOf(parameter).getUnitGroup(); will(returnValue(UnitGroup.UNITS_NONE));
oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(gvalue));
oneOf(modifier1).getCurrentSIValue(simulation); will(returnValue(0.2));
oneOf(modifier1).getUnitGroup(); will(returnValue(UnitGroup.UNITS_LENGTH));
oneOf(listener).evaluated(point, new Value[] {
new Value(0.2, UnitGroup.UNITS_LENGTH.getDefaultUnit()),
new Value(0.3, UnitGroup.UNITS_LENGTH.getDefaultUnit())
- }, dref, pvalue, gvalue);
+ }, dref, pvalueValue, gvalue);
}});
// @formatter:on
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = -0.43;
- final double dref = 0.33;
+ final Value dref = new Value(0.33, Unit.NOUNIT2);
final double pvalue = 9.81;
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
- oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+ oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
+ oneOf(parameter).getUnitGroup(); will(returnValue(UnitGroup.UNITS_NONE));
oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(Double.NaN));
}});
// @formatter:on
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = 0.98;
- final double dref = 0.33;
+ final Value dref = new Value(ddist, Unit.NOUNIT2);
final Point point = new Point(p1, p2);
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
- oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+ oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
oneOf(modifier1).getCurrentSIValue(simulation); will(returnValue(0.2));
oneOf(modifier1).getUnitGroup(); will(returnValue(UnitGroup.UNITS_LENGTH));
oneOf(modifier2).getCurrentSIValue(simulation); will(returnValue(0.3));
oneOf(listener).evaluated(point, new Value[] {
new Value(0.2, UnitGroup.UNITS_LENGTH.getDefaultUnit()),
new Value(0.3, UnitGroup.UNITS_LENGTH.getDefaultUnit())
- }, dref, Double.NaN, 1.98E200);
+ }, dref, null, 1.98E200);
}});
// @formatter:on
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = Double.NaN;
- final double dref = 0.33;
+ final Value dref = new Value(0.33, Unit.NOUNIT2);
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
- oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+ oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
}});
// @formatter:on
value = new TestValue();
sim = new Simulation(new Rocket());
- gm = new GenericModifier<TestGenericModifier.TestValue>("Test modifier", null,
+ Object related = new Object();
+
+ gm = new GenericModifier<TestGenericModifier.TestValue>("Test modifier", "Description", related,
UnitGroup.UNITS_NONE, 2.0, TestValue.class, "value") {
@Override
protected TestValue getModifiedObject(Simulation simulation) {
import org.junit.Test;
public class ValueTest {
-
+
@Test
public void testValues() {
Value v1, v2;
assertTrue(v1.compareTo(v1) == 0);
assertTrue(v2.compareTo(v2) == 0);
- v2.setUnit(UnitGroup.UNITS_TEMPERATURE.findApproximate("K"));
+ v2 = new Value(283.15, UnitGroup.UNITS_TEMPERATURE.findApproximate("K"));
assertTrue(v1.compareTo(v2) > 0);
assertTrue(v2.compareTo(v1) < 0);
assertEquals("283 K", v2.toString());
-
- v2.setUnit(UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
+
+ v2 = new Value(283.15, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
assertTrue(v1.compareTo(v2) < 0);
assertTrue(v2.compareTo(v1) > 0);
-
- v1.setValue(Double.NaN);
+
+ v1 = new Value(Double.NaN, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
assertTrue(v1.compareTo(v2) > 0);
assertTrue(v2.compareTo(v1) < 0);
- v2.setValue(Double.NaN);
+ v2 = new Value(Double.NaN, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
assertTrue(v1.compareTo(v2) == 0);
assertTrue(v1.compareTo(v2) == 0);
assertEquals("N/A", v1.toString());
public class TextUtilTest {
@Test
- public void textHexString() {
+ public void testHexString() {
assertEquals("", TextUtil.hexString(new byte[0]));
assertEquals("00", TextUtil.hexString(new byte[] { 0x00 }));
assertEquals("ff", TextUtil.hexString(new byte[] { (byte) 0xff }));
-
- for (int i=0; i <= 0xff; i++) {
+
+ for (int i = 0; i <= 0xff; i++) {
assertEquals(String.format("%02x", i), TextUtil.hexString(new byte[] { (byte) i }));
}
- assertEquals("0f1e2d3c4b5a6978", TextUtil.hexString(new byte[] {
+ assertEquals("0f1e2d3c4b5a6978", TextUtil.hexString(new byte[] {
0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, 0x69, 0x78
}));
Random rnd = new Random();
- for (int count=0; count<10; count++) {
+ for (int count = 0; count < 10; count++) {
int n = rnd.nextInt(100);
byte[] bytes = new byte[n];
rnd.nextBytes(bytes);
StringBuilder sb = new StringBuilder();
- for (byte b: bytes) {
+ for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xFF));
}
assertEquals(sb.toString(), TextUtil.hexString(bytes));
}
}
-
+
@Test
public void specialCaseTest() {
- assertEquals("NaN",TextUtil.doubleToString(Double.NaN));
- assertEquals("Inf",TextUtil.doubleToString(Double.POSITIVE_INFINITY));
- assertEquals("-Inf",TextUtil.doubleToString(Double.NEGATIVE_INFINITY));
- assertEquals("0",TextUtil.doubleToString(0.0));
- assertEquals("0",TextUtil.doubleToString(MathUtil.EPSILON/3));
- assertEquals("0",TextUtil.doubleToString(-MathUtil.EPSILON/3));
+ assertEquals("NaN", TextUtil.doubleToString(Double.NaN));
+ assertEquals("Inf", TextUtil.doubleToString(Double.POSITIVE_INFINITY));
+ assertEquals("-Inf", TextUtil.doubleToString(Double.NEGATIVE_INFINITY));
+ assertEquals("0", TextUtil.doubleToString(0.0));
+ assertEquals("0", TextUtil.doubleToString(MathUtil.EPSILON / 3));
+ assertEquals("0", TextUtil.doubleToString(-MathUtil.EPSILON / 3));
}
@Test
public void longTest() {
- assertEquals("3.1416e-5", TextUtil.doubleToString(PI*1e-5));
- assertEquals("3.1416e-4", TextUtil.doubleToString(PI*1e-4));
- assertEquals("0.0031416", TextUtil.doubleToString(PI*1e-3));
- assertEquals("0.031416", TextUtil.doubleToString(PI*1e-2));
- assertEquals("0.31416", TextUtil.doubleToString(PI*1e-1));
- assertEquals("3.1416", TextUtil.doubleToString(PI));
- assertEquals("31.416", TextUtil.doubleToString(PI*1e1));
- assertEquals("314.16", TextUtil.doubleToString(PI*1e2));
- assertEquals("3141.6", TextUtil.doubleToString(PI*1e3));
- assertEquals("31416", TextUtil.doubleToString(PI*1e4));
- assertEquals("314159", TextUtil.doubleToString(PI*1e5));
- assertEquals("3141593", TextUtil.doubleToString(PI*1e6));
- assertEquals("31415927", TextUtil.doubleToString(PI*1e7));
- assertEquals("3.1416e8", TextUtil.doubleToString(PI*1e8));
- assertEquals("3.1416e9", TextUtil.doubleToString(PI*1e9));
- assertEquals("3.1416e10", TextUtil.doubleToString(PI*1e10));
-
- assertEquals("-3.1416e-5", TextUtil.doubleToString(-PI*1e-5));
- assertEquals("-3.1416e-4", TextUtil.doubleToString(-PI*1e-4));
- assertEquals("-0.0031416", TextUtil.doubleToString(-PI*1e-3));
- assertEquals("-0.031416", TextUtil.doubleToString(-PI*1e-2));
- assertEquals("-0.31416", TextUtil.doubleToString(-PI*1e-1));
- assertEquals("-3.1416", TextUtil.doubleToString(-PI));
- assertEquals("-31.416", TextUtil.doubleToString(-PI*1e1));
- assertEquals("-314.16", TextUtil.doubleToString(-PI*1e2));
- assertEquals("-3141.6", TextUtil.doubleToString(-PI*1e3));
- assertEquals("-31416", TextUtil.doubleToString(-PI*1e4));
- assertEquals("-314159", TextUtil.doubleToString(-PI*1e5));
- assertEquals("-3141593", TextUtil.doubleToString(-PI*1e6));
- assertEquals("-31415927", TextUtil.doubleToString(-PI*1e7));
- assertEquals("-3.1416e8", TextUtil.doubleToString(-PI*1e8));
- assertEquals("-3.1416e9", TextUtil.doubleToString(-PI*1e9));
- assertEquals("-3.1416e10", TextUtil.doubleToString(-PI*1e10));
-
+ assertEquals("3.1416e-5", TextUtil.doubleToString(PI * 1e-5));
+ assertEquals("3.1416e-4", TextUtil.doubleToString(PI * 1e-4));
+ assertEquals("0.0031416", TextUtil.doubleToString(PI * 1e-3));
+ assertEquals("0.031416", TextUtil.doubleToString(PI * 1e-2));
+ assertEquals("0.31416", TextUtil.doubleToString(PI * 1e-1));
+ assertEquals("3.1416", TextUtil.doubleToString(PI));
+ assertEquals("31.416", TextUtil.doubleToString(PI * 1e1));
+ assertEquals("314.16", TextUtil.doubleToString(PI * 1e2));
+ assertEquals("3141.6", TextUtil.doubleToString(PI * 1e3));
+ assertEquals("31416", TextUtil.doubleToString(PI * 1e4));
+ assertEquals("314159", TextUtil.doubleToString(PI * 1e5));
+ assertEquals("3141593", TextUtil.doubleToString(PI * 1e6));
+ assertEquals("31415927", TextUtil.doubleToString(PI * 1e7));
+ assertEquals("3.1416e8", TextUtil.doubleToString(PI * 1e8));
+ assertEquals("3.1416e9", TextUtil.doubleToString(PI * 1e9));
+ assertEquals("3.1416e10", TextUtil.doubleToString(PI * 1e10));
+
+ assertEquals("-3.1416e-5", TextUtil.doubleToString(-PI * 1e-5));
+ assertEquals("-3.1416e-4", TextUtil.doubleToString(-PI * 1e-4));
+ assertEquals("-0.0031416", TextUtil.doubleToString(-PI * 1e-3));
+ assertEquals("-0.031416", TextUtil.doubleToString(-PI * 1e-2));
+ assertEquals("-0.31416", TextUtil.doubleToString(-PI * 1e-1));
+ assertEquals("-3.1416", TextUtil.doubleToString(-PI));
+ assertEquals("-31.416", TextUtil.doubleToString(-PI * 1e1));
+ assertEquals("-314.16", TextUtil.doubleToString(-PI * 1e2));
+ assertEquals("-3141.6", TextUtil.doubleToString(-PI * 1e3));
+ assertEquals("-31416", TextUtil.doubleToString(-PI * 1e4));
+ assertEquals("-314159", TextUtil.doubleToString(-PI * 1e5));
+ assertEquals("-3141593", TextUtil.doubleToString(-PI * 1e6));
+ assertEquals("-31415927", TextUtil.doubleToString(-PI * 1e7));
+ assertEquals("-3.1416e8", TextUtil.doubleToString(-PI * 1e8));
+ assertEquals("-3.1416e9", TextUtil.doubleToString(-PI * 1e9));
+ assertEquals("-3.1416e10", TextUtil.doubleToString(-PI * 1e10));
+
}
@Test
public void shortTest() {
double p = 3.1;
- assertEquals("3.1e-5", TextUtil.doubleToString(p*1e-5));
- assertEquals("3.1e-4", TextUtil.doubleToString(p*1e-4));
- assertEquals("0.0031", TextUtil.doubleToString(p*1e-3));
- assertEquals("0.031", TextUtil.doubleToString(p*1e-2));
- assertEquals("0.31", TextUtil.doubleToString(p*1e-1));
- assertEquals("3.1", TextUtil.doubleToString(p));
- assertEquals("31", TextUtil.doubleToString(p*1e1));
- assertEquals("310", TextUtil.doubleToString(p*1e2));
- assertEquals("3100", TextUtil.doubleToString(p*1e3));
- assertEquals("31000", TextUtil.doubleToString(p*1e4));
- assertEquals("3.1e5", TextUtil.doubleToString(p*1e5));
- assertEquals("3.1e6", TextUtil.doubleToString(p*1e6));
- assertEquals("3.1e7", TextUtil.doubleToString(p*1e7));
- assertEquals("3.1e8", TextUtil.doubleToString(p*1e8));
- assertEquals("3.1e9", TextUtil.doubleToString(p*1e9));
- assertEquals("3.1e10", TextUtil.doubleToString(p*1e10));
-
- assertEquals("-3.1e-5", TextUtil.doubleToString(-p*1e-5));
- assertEquals("-3.1e-4", TextUtil.doubleToString(-p*1e-4));
- assertEquals("-0.0031", TextUtil.doubleToString(-p*1e-3));
- assertEquals("-0.031", TextUtil.doubleToString(-p*1e-2));
- assertEquals("-0.31", TextUtil.doubleToString(-p*1e-1));
- assertEquals("-3.1", TextUtil.doubleToString(-p));
- assertEquals("-31", TextUtil.doubleToString(-p*1e1));
- assertEquals("-310", TextUtil.doubleToString(-p*1e2));
- assertEquals("-3100", TextUtil.doubleToString(-p*1e3));
- assertEquals("-31000", TextUtil.doubleToString(-p*1e4));
- assertEquals("-3.1e5", TextUtil.doubleToString(-p*1e5));
- assertEquals("-3.1e6", TextUtil.doubleToString(-p*1e6));
- assertEquals("-3.1e7", TextUtil.doubleToString(-p*1e7));
- assertEquals("-3.1e8", TextUtil.doubleToString(-p*1e8));
- assertEquals("-3.1e9", TextUtil.doubleToString(-p*1e9));
- assertEquals("-3.1e10", TextUtil.doubleToString(-p*1e10));
-
+ assertEquals("3.1e-5", TextUtil.doubleToString(p * 1e-5));
+ assertEquals("3.1e-4", TextUtil.doubleToString(p * 1e-4));
+ assertEquals("0.0031", TextUtil.doubleToString(p * 1e-3));
+ assertEquals("0.031", TextUtil.doubleToString(p * 1e-2));
+ assertEquals("0.31", TextUtil.doubleToString(p * 1e-1));
+ assertEquals("3.1", TextUtil.doubleToString(p));
+ assertEquals("31", TextUtil.doubleToString(p * 1e1));
+ assertEquals("310", TextUtil.doubleToString(p * 1e2));
+ assertEquals("3100", TextUtil.doubleToString(p * 1e3));
+ assertEquals("31000", TextUtil.doubleToString(p * 1e4));
+ assertEquals("3.1e5", TextUtil.doubleToString(p * 1e5));
+ assertEquals("3.1e6", TextUtil.doubleToString(p * 1e6));
+ assertEquals("3.1e7", TextUtil.doubleToString(p * 1e7));
+ assertEquals("3.1e8", TextUtil.doubleToString(p * 1e8));
+ assertEquals("3.1e9", TextUtil.doubleToString(p * 1e9));
+ assertEquals("3.1e10", TextUtil.doubleToString(p * 1e10));
+
+ assertEquals("-3.1e-5", TextUtil.doubleToString(-p * 1e-5));
+ assertEquals("-3.1e-4", TextUtil.doubleToString(-p * 1e-4));
+ assertEquals("-0.0031", TextUtil.doubleToString(-p * 1e-3));
+ assertEquals("-0.031", TextUtil.doubleToString(-p * 1e-2));
+ assertEquals("-0.31", TextUtil.doubleToString(-p * 1e-1));
+ assertEquals("-3.1", TextUtil.doubleToString(-p));
+ assertEquals("-31", TextUtil.doubleToString(-p * 1e1));
+ assertEquals("-310", TextUtil.doubleToString(-p * 1e2));
+ assertEquals("-3100", TextUtil.doubleToString(-p * 1e3));
+ assertEquals("-31000", TextUtil.doubleToString(-p * 1e4));
+ assertEquals("-3.1e5", TextUtil.doubleToString(-p * 1e5));
+ assertEquals("-3.1e6", TextUtil.doubleToString(-p * 1e6));
+ assertEquals("-3.1e7", TextUtil.doubleToString(-p * 1e7));
+ assertEquals("-3.1e8", TextUtil.doubleToString(-p * 1e8));
+ assertEquals("-3.1e9", TextUtil.doubleToString(-p * 1e9));
+ assertEquals("-3.1e10", TextUtil.doubleToString(-p * 1e10));
+
p = 3;
- assertEquals("3e-5", TextUtil.doubleToString(p*1e-5));
- assertEquals("3e-4", TextUtil.doubleToString(p*1e-4));
- assertEquals("3e-3", TextUtil.doubleToString(p*1e-3));
- assertEquals("0.03", TextUtil.doubleToString(p*1e-2));
- assertEquals("0.3", TextUtil.doubleToString(p*1e-1));
- assertEquals("3", TextUtil.doubleToString(p));
- assertEquals("30", TextUtil.doubleToString(p*1e1));
- assertEquals("300", TextUtil.doubleToString(p*1e2));
- assertEquals("3e3", TextUtil.doubleToString(p*1e3));
- assertEquals("3e4", TextUtil.doubleToString(p*1e4));
- assertEquals("3e5", TextUtil.doubleToString(p*1e5));
- assertEquals("3e6", TextUtil.doubleToString(p*1e6));
- assertEquals("3e7", TextUtil.doubleToString(p*1e7));
- assertEquals("3e8", TextUtil.doubleToString(p*1e8));
- assertEquals("3e9", TextUtil.doubleToString(p*1e9));
- assertEquals("3e10", TextUtil.doubleToString(p*1e10));
-
- assertEquals("-3e-5", TextUtil.doubleToString(-p*1e-5));
- assertEquals("-3e-4", TextUtil.doubleToString(-p*1e-4));
- assertEquals("-3e-3", TextUtil.doubleToString(-p*1e-3));
- assertEquals("-0.03", TextUtil.doubleToString(-p*1e-2));
- assertEquals("-0.3", TextUtil.doubleToString(-p*1e-1));
- assertEquals("-3", TextUtil.doubleToString(-p));
- assertEquals("-30", TextUtil.doubleToString(-p*1e1));
- assertEquals("-300", TextUtil.doubleToString(-p*1e2));
- assertEquals("-3e3", TextUtil.doubleToString(-p*1e3));
- assertEquals("-3e4", TextUtil.doubleToString(-p*1e4));
- assertEquals("-3e5", TextUtil.doubleToString(-p*1e5));
- assertEquals("-3e6", TextUtil.doubleToString(-p*1e6));
- assertEquals("-3e7", TextUtil.doubleToString(-p*1e7));
- assertEquals("-3e8", TextUtil.doubleToString(-p*1e8));
- assertEquals("-3e9", TextUtil.doubleToString(-p*1e9));
- assertEquals("-3e10", TextUtil.doubleToString(-p*1e10));
-
+ assertEquals("3e-5", TextUtil.doubleToString(p * 1e-5));
+ assertEquals("3e-4", TextUtil.doubleToString(p * 1e-4));
+ assertEquals("3e-3", TextUtil.doubleToString(p * 1e-3));
+ assertEquals("0.03", TextUtil.doubleToString(p * 1e-2));
+ assertEquals("0.3", TextUtil.doubleToString(p * 1e-1));
+ assertEquals("3", TextUtil.doubleToString(p));
+ assertEquals("30", TextUtil.doubleToString(p * 1e1));
+ assertEquals("300", TextUtil.doubleToString(p * 1e2));
+ assertEquals("3e3", TextUtil.doubleToString(p * 1e3));
+ assertEquals("3e4", TextUtil.doubleToString(p * 1e4));
+ assertEquals("3e5", TextUtil.doubleToString(p * 1e5));
+ assertEquals("3e6", TextUtil.doubleToString(p * 1e6));
+ assertEquals("3e7", TextUtil.doubleToString(p * 1e7));
+ assertEquals("3e8", TextUtil.doubleToString(p * 1e8));
+ assertEquals("3e9", TextUtil.doubleToString(p * 1e9));
+ assertEquals("3e10", TextUtil.doubleToString(p * 1e10));
+
+ assertEquals("-3e-5", TextUtil.doubleToString(-p * 1e-5));
+ assertEquals("-3e-4", TextUtil.doubleToString(-p * 1e-4));
+ assertEquals("-3e-3", TextUtil.doubleToString(-p * 1e-3));
+ assertEquals("-0.03", TextUtil.doubleToString(-p * 1e-2));
+ assertEquals("-0.3", TextUtil.doubleToString(-p * 1e-1));
+ assertEquals("-3", TextUtil.doubleToString(-p));
+ assertEquals("-30", TextUtil.doubleToString(-p * 1e1));
+ assertEquals("-300", TextUtil.doubleToString(-p * 1e2));
+ assertEquals("-3e3", TextUtil.doubleToString(-p * 1e3));
+ assertEquals("-3e4", TextUtil.doubleToString(-p * 1e4));
+ assertEquals("-3e5", TextUtil.doubleToString(-p * 1e5));
+ assertEquals("-3e6", TextUtil.doubleToString(-p * 1e6));
+ assertEquals("-3e7", TextUtil.doubleToString(-p * 1e7));
+ assertEquals("-3e8", TextUtil.doubleToString(-p * 1e8));
+ assertEquals("-3e9", TextUtil.doubleToString(-p * 1e9));
+ assertEquals("-3e10", TextUtil.doubleToString(-p * 1e10));
+
}
@Test
assertEquals("1.001", TextUtil.doubleToString(1.00096));
-
+
/*
* Not testing with 1.00015 because it might be changed during number formatting
* calculations. Its rounding is basically arbitrary anyway.
*/
-
+
assertEquals("1.0002e-5", TextUtil.doubleToString(1.0001500001e-5));
assertEquals("1.0001e-5", TextUtil.doubleToString(1.0001499999e-5));
assertEquals("1.0002e-4", TextUtil.doubleToString(1.0001500001e-4));
assertEquals("1.0001e-4", TextUtil.doubleToString(1.0001499999e-4));
assertEquals("0.0010002", TextUtil.doubleToString(1.0001500001e-3));
assertEquals("0.0010001", TextUtil.doubleToString(1.0001499999e-3));
- assertEquals("0.010002", TextUtil.doubleToString(1.0001500001e-2));
- assertEquals("0.010001", TextUtil.doubleToString(1.0001499999e-2));
- assertEquals("0.10002", TextUtil.doubleToString(1.0001500001e-1));
- assertEquals("0.10001", TextUtil.doubleToString(1.0001499999e-1));
- assertEquals("1.0002", TextUtil.doubleToString(1.0001500001));
- assertEquals("1.0001", TextUtil.doubleToString(1.0001499999));
- assertEquals("10.002", TextUtil.doubleToString(1.0001500001e1));
- assertEquals("10.001", TextUtil.doubleToString(1.0001499999e1));
- assertEquals("100.02", TextUtil.doubleToString(1.0001500001e2));
- assertEquals("100.01", TextUtil.doubleToString(1.0001499999e2));
- assertEquals("1000.2", TextUtil.doubleToString(1.0001500001e3));
- assertEquals("1000.1", TextUtil.doubleToString(1.0001499999e3));
- assertEquals("10002", TextUtil.doubleToString(1.0001500001e4));
- assertEquals("10001", TextUtil.doubleToString(1.0001499999e4));
- assertEquals("100012", TextUtil.doubleToString(1.00011500001e5));
- assertEquals("100011", TextUtil.doubleToString(1.00011499999e5));
- assertEquals("1000112", TextUtil.doubleToString(1.000111500001e6));
- assertEquals("1000111", TextUtil.doubleToString(1.000111499999e6));
- assertEquals("10001112", TextUtil.doubleToString(1.0001111500001e7));
- assertEquals("10001111", TextUtil.doubleToString(1.0001111499999e7));
- assertEquals("1.0002e8", TextUtil.doubleToString(1.0001500001e8));
- assertEquals("1.0001e8", TextUtil.doubleToString(1.0001499999e8));
- assertEquals("1.0002e9", TextUtil.doubleToString(1.0001500001e9));
- assertEquals("1.0001e9", TextUtil.doubleToString(1.0001499999e9));
+ assertEquals("0.010002", TextUtil.doubleToString(1.0001500001e-2));
+ assertEquals("0.010001", TextUtil.doubleToString(1.0001499999e-2));
+ assertEquals("0.10002", TextUtil.doubleToString(1.0001500001e-1));
+ assertEquals("0.10001", TextUtil.doubleToString(1.0001499999e-1));
+ assertEquals("1.0002", TextUtil.doubleToString(1.0001500001));
+ assertEquals("1.0001", TextUtil.doubleToString(1.0001499999));
+ assertEquals("10.002", TextUtil.doubleToString(1.0001500001e1));
+ assertEquals("10.001", TextUtil.doubleToString(1.0001499999e1));
+ assertEquals("100.02", TextUtil.doubleToString(1.0001500001e2));
+ assertEquals("100.01", TextUtil.doubleToString(1.0001499999e2));
+ assertEquals("1000.2", TextUtil.doubleToString(1.0001500001e3));
+ assertEquals("1000.1", TextUtil.doubleToString(1.0001499999e3));
+ assertEquals("10002", TextUtil.doubleToString(1.0001500001e4));
+ assertEquals("10001", TextUtil.doubleToString(1.0001499999e4));
+ assertEquals("100012", TextUtil.doubleToString(1.00011500001e5));
+ assertEquals("100011", TextUtil.doubleToString(1.00011499999e5));
+ assertEquals("1000112", TextUtil.doubleToString(1.000111500001e6));
+ assertEquals("1000111", TextUtil.doubleToString(1.000111499999e6));
+ assertEquals("10001112", TextUtil.doubleToString(1.0001111500001e7));
+ assertEquals("10001111", TextUtil.doubleToString(1.0001111499999e7));
+ assertEquals("1.0002e8", TextUtil.doubleToString(1.0001500001e8));
+ assertEquals("1.0001e8", TextUtil.doubleToString(1.0001499999e8));
+ assertEquals("1.0002e9", TextUtil.doubleToString(1.0001500001e9));
+ assertEquals("1.0001e9", TextUtil.doubleToString(1.0001499999e9));
assertEquals("1.0002e10", TextUtil.doubleToString(1.0001500001e10));
assertEquals("1.0001e10", TextUtil.doubleToString(1.0001499999e10));
assertEquals("-1.0001e-4", TextUtil.doubleToString(-1.0001499999e-4));
assertEquals("-0.0010002", TextUtil.doubleToString(-1.0001500001e-3));
assertEquals("-0.0010001", TextUtil.doubleToString(-1.0001499999e-3));
- assertEquals("-0.010002", TextUtil.doubleToString(-1.0001500001e-2));
- assertEquals("-0.010001", TextUtil.doubleToString(-1.0001499999e-2));
- assertEquals("-0.10002", TextUtil.doubleToString(-1.0001500001e-1));
- assertEquals("-0.10001", TextUtil.doubleToString(-1.0001499999e-1));
- assertEquals("-1.0002", TextUtil.doubleToString(-1.0001500001));
- assertEquals("-1.0001", TextUtil.doubleToString(-1.0001499999));
- assertEquals("-10.002", TextUtil.doubleToString(-1.0001500001e1));
- assertEquals("-10.001", TextUtil.doubleToString(-1.0001499999e1));
- assertEquals("-100.02", TextUtil.doubleToString(-1.0001500001e2));
- assertEquals("-100.01", TextUtil.doubleToString(-1.0001499999e2));
- assertEquals("-1000.2", TextUtil.doubleToString(-1.0001500001e3));
- assertEquals("-1000.1", TextUtil.doubleToString(-1.0001499999e3));
- assertEquals("-10002", TextUtil.doubleToString(-1.0001500001e4));
- assertEquals("-10001", TextUtil.doubleToString(-1.0001499999e4));
- assertEquals("-100012", TextUtil.doubleToString(-1.00011500001e5));
- assertEquals("-100011", TextUtil.doubleToString(-1.00011499999e5));
- assertEquals("-1000112", TextUtil.doubleToString(-1.000111500001e6));
- assertEquals("-1000111", TextUtil.doubleToString(-1.000111499999e6));
- assertEquals("-10001112", TextUtil.doubleToString(-1.0001111500001e7));
- assertEquals("-10001111", TextUtil.doubleToString(-1.0001111499999e7));
- assertEquals("-1.0002e8", TextUtil.doubleToString(-1.0001500001e8));
- assertEquals("-1.0001e8", TextUtil.doubleToString(-1.0001499999e8));
- assertEquals("-1.0002e9", TextUtil.doubleToString(-1.0001500001e9));
- assertEquals("-1.0001e9", TextUtil.doubleToString(-1.0001499999e9));
+ assertEquals("-0.010002", TextUtil.doubleToString(-1.0001500001e-2));
+ assertEquals("-0.010001", TextUtil.doubleToString(-1.0001499999e-2));
+ assertEquals("-0.10002", TextUtil.doubleToString(-1.0001500001e-1));
+ assertEquals("-0.10001", TextUtil.doubleToString(-1.0001499999e-1));
+ assertEquals("-1.0002", TextUtil.doubleToString(-1.0001500001));
+ assertEquals("-1.0001", TextUtil.doubleToString(-1.0001499999));
+ assertEquals("-10.002", TextUtil.doubleToString(-1.0001500001e1));
+ assertEquals("-10.001", TextUtil.doubleToString(-1.0001499999e1));
+ assertEquals("-100.02", TextUtil.doubleToString(-1.0001500001e2));
+ assertEquals("-100.01", TextUtil.doubleToString(-1.0001499999e2));
+ assertEquals("-1000.2", TextUtil.doubleToString(-1.0001500001e3));
+ assertEquals("-1000.1", TextUtil.doubleToString(-1.0001499999e3));
+ assertEquals("-10002", TextUtil.doubleToString(-1.0001500001e4));
+ assertEquals("-10001", TextUtil.doubleToString(-1.0001499999e4));
+ assertEquals("-100012", TextUtil.doubleToString(-1.00011500001e5));
+ assertEquals("-100011", TextUtil.doubleToString(-1.00011499999e5));
+ assertEquals("-1000112", TextUtil.doubleToString(-1.000111500001e6));
+ assertEquals("-1000111", TextUtil.doubleToString(-1.000111499999e6));
+ assertEquals("-10001112", TextUtil.doubleToString(-1.0001111500001e7));
+ assertEquals("-10001111", TextUtil.doubleToString(-1.0001111499999e7));
+ assertEquals("-1.0002e8", TextUtil.doubleToString(-1.0001500001e8));
+ assertEquals("-1.0001e8", TextUtil.doubleToString(-1.0001499999e8));
+ assertEquals("-1.0002e9", TextUtil.doubleToString(-1.0001500001e9));
+ assertEquals("-1.0001e9", TextUtil.doubleToString(-1.0001499999e9));
assertEquals("-1.0002e10", TextUtil.doubleToString(-1.0001500001e10));
assertEquals("-1.0001e10", TextUtil.doubleToString(-1.0001499999e10));
@Test
public void randomTest() {
- for (int i=0; i<10000; i++) {
+ for (int i = 0; i < 10000; i++) {
double orig = Math.random();
double result;
- double expected = Math.rint(orig*100000) / 100000.0;
+ double expected = Math.rint(orig * 100000) / 100000.0;
if (orig < 0.1)
continue;