Implement feature to compute the optimum delay for a simulation.

This commit is contained in:
kruland2607 2014-04-08 13:09:38 -05:00
parent 4565cdcd86
commit a34d41d6af
30 changed files with 487 additions and 189 deletions

View File

@ -430,6 +430,8 @@ simpanel.col.Motors = Motors
simpanel.col.Configuration = Configuration
simpanel.col.Velocityoffrod = Velocity off rod
simpanel.col.Velocityatdeploy = Velocity at deployment
simpanel.col.OptimumCoastTime = Optimum delay
simpanel.col.OptimumCoastTime.ttip = The time between last motor burnout and maximum possible altitude.
simpanel.col.Apogee = Apogee
simpanel.col.Maxvelocity = Max. velocity
simpanel.col.Maxacceleration = Max. acceleration

View File

@ -536,6 +536,7 @@ public class OpenRocketSaver extends RocketSaver {
StringBuilder sb = new StringBuilder();
sb.append("<databranch name=\"");
sb.append(TextUtil.escapeXML(branch.getBranchName()));
sb.append("\" ");
// Kevins version where typekeys are used
/*
@ -547,7 +548,19 @@ public class OpenRocketSaver extends RocketSaver {
}
*/
sb.append("\" types=\"");
if (!Double.isNaN(branch.getOptimumAltitude())) {
sb.append("optimumAltitude=\"");
sb.append(branch.getOptimumAltitude());
sb.append("\" ");
}
if (!Double.isNaN(branch.getTimeToOptimumAltitude())) {
sb.append("timeToOptimumAltitude=\"");
sb.append(branch.getTimeToOptimumAltitude());
sb.append("\" ");
}
sb.append("types=\"");
for (int i = 0; i < types.length; i++) {
if (i > 0)
sb.append(",");
@ -590,8 +603,6 @@ public class OpenRocketSaver extends RocketSaver {
writeln("</databranch>");
}
/* TODO: LOW: This is largely duplicated from above! */
private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
int count = 0;

View File

@ -42,6 +42,22 @@ class FlightDataBranchHandler extends AbstractElementHandler {
branch = new FlightDataBranch(name, types);
}
/**
* @param timeToOptimumAltitude
* @see net.sf.openrocket.simulation.FlightDataBranch#setTimeToOptimumAltitude(double)
*/
public void setTimeToOptimumAltitude(double timeToOptimumAltitude) {
branch.setTimeToOptimumAltitude(timeToOptimumAltitude);
}
/**
* @param optimumAltitude
* @see net.sf.openrocket.simulation.FlightDataBranch#setOptimumAltitude(double)
*/
public void setOptimumAltitude(double optimumAltitude) {
branch.setOptimumAltitude(optimumAltitude);
}
// Find the full flight data type given name only
// Note: this way of doing it requires that custom expressions always come before flight data in the file,
// not the nicest but this is always the case anyway.

View File

@ -48,6 +48,23 @@ class FlightDataHandler extends AbstractElementHandler {
dataHandler = new FlightDataBranchHandler(attributes.get("name"),
attributes.get("types"),
simHandler, context);
if (attributes.get("optimumAltitude") != null) {
double optimumAltitude = Double.NaN;
try {
optimumAltitude = Double.parseDouble(attributes.get("optimumAltitude"));
} catch (NumberFormatException ignore) {
}
dataHandler.setOptimumAltitude(optimumAltitude);
}
if (attributes.get("timeToOptimumAltitude") != null) {
double timeToOptimumAltitude = Double.NaN;
try {
timeToOptimumAltitude = Double.parseDouble(attributes.get("timeToOptimumAltitude"));
} catch (NumberFormatException ignore) {
}
dataHandler.setTimeToOptimumAltitude(timeToOptimumAltitude);
}
return dataHandler;
}

View File

@ -21,6 +21,7 @@ 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;
import net.sf.openrocket.simulation.listeners.system.OptimumCoastListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
@ -120,8 +121,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Coordinate originVelocity = status.getRocketVelocity();
try {
double maxAlt = Double.NEGATIVE_INFINITY;
// Start the simulation
while (handleEvents()) {
@ -149,8 +148,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getConfiguration().getRocket(),
new Pair<Double, Double>(oldAlt, status.getRocketPosition().z)));
if (status.getRocketPosition().z > maxAlt) {
maxAlt = status.getRocketPosition().z;
if (status.getRocketPosition().z > status.getMaxAlt()) {
status.setMaxAlt(status.getRocketPosition().z);
}
@ -189,7 +188,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for apogee
if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) {
if (!status.isApogeeReached() && status.getRocketPosition().z < status.getMaxAlt() - 0.01) {
status.setMaxAltTime(status.getSimulationTime());
addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(),
status.getConfiguration().getRocket()));
}
@ -464,6 +464,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Mark apogee as reached
status.setApogeeReached(true);
status.getFlightData().addEvent(event);
// This apogee event might be the optimum if recovery has not already happened.
if (status.getSimulationConditions().isCalculateExtras() && status.getDeployedRecoveryDevices().size() == 0) {
status.getFlightData().setOptimumAltitude(status.getMaxAlt());
status.getFlightData().setTimeToOptimumAltitude(status.getMaxAltTime());
}
break;
case RECOVERY_DEVICE_DEPLOYMENT:
@ -501,6 +506,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.setLiftoff(true);
status.getDeployedRecoveryDevices().add((RecoveryDevice) c);
// If we haven't already reached apogee, then we need to compute the actual coast time
// to determine the optimum altitude.
if (status.getSimulationConditions().isCalculateExtras() && !status.isApogeeReached()) {
FlightData coastStatus = computeCoastTime();
status.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude());
status.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee());
}
this.currentStepper = this.landingStepper;
this.status = currentStepper.initialize(status);
@ -601,5 +614,17 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
}
private FlightData computeCoastTime() {
try {
SimulationConditions conds = status.getSimulationConditions().clone();
conds.getSimulationListenerList().add(OptimumCoastListener.INSTANCE);
BasicEventSimulationEngine e = new BasicEventSimulationEngine();
FlightData d = e.simulate(conds);
return d;
} catch (Exception e) {
log.warn("Exception computing coast time: ", e);
return null;
}
}
}

View File

@ -36,6 +36,14 @@ public class FlightDataBranch implements Monitorable {
private final Map<FlightDataType, Double> maxValues = new HashMap<FlightDataType, Double>();
private final Map<FlightDataType, Double> minValues = new HashMap<FlightDataType, Double>();
/**
* time for the rocket to reach apogee if the flight had been no recovery deployment
*/
private double timeToOptimumAltitude = Double.NaN;
/**
* Altitude the rocket would reach if there had been no recovery deployment.
*/
private double optimumAltitude = Double.NaN;
private final ArrayList<FlightEvent> events = new ArrayList<FlightEvent>();
@ -73,7 +81,7 @@ public class FlightDataBranch implements Monitorable {
*/
public FlightDataBranch() {
branchName = "Empty branch";
for (FlightDataType type : FlightDataType.ALL_TYPES){
for (FlightDataType type : FlightDataType.ALL_TYPES) {
this.setValue(type, Double.NaN);
}
this.immute();
@ -118,7 +126,7 @@ public class FlightDataBranch implements Monitorable {
maxValues.put(type, value);
}
if (list.size() > 0){
if (list.size() > 0) {
list.set(list.size() - 1, value);
}
@ -219,6 +227,50 @@ public class FlightDataBranch implements Monitorable {
}
/**
* @return the timeToOptimumAltitude
*/
public double getTimeToOptimumAltitude() {
return timeToOptimumAltitude;
}
/**
* @param timeToOptimumAltitude the timeToOptimumAltitude to set
*/
public void setTimeToOptimumAltitude(double timeToOptimumAltitude) {
this.timeToOptimumAltitude = timeToOptimumAltitude;
}
/**
* @return the optimumAltitude
*/
public double getOptimumAltitude() {
return optimumAltitude;
}
/**
* @param optimumAltitude the optimumAltitude to set
*/
public void setOptimumAltitude(double optimumAltitude) {
this.optimumAltitude = optimumAltitude;
}
public double getOptimumDelay() {
if (Double.isNaN(timeToOptimumAltitude)) {
return Double.NaN;
}
// TODO - we really want the first burnout of this stage. which
// could be computed as the first burnout after the last stage separation event.
// however, that's not quite so concise
FlightEvent e = getLastEvent(FlightEvent.Type.BURNOUT);
if (e != null) {
return timeToOptimumAltitude - e.getTime();
}
return Double.NaN;
}
/**
* Add a flight event to this branch.
*
@ -241,6 +293,34 @@ public class FlightDataBranch implements Monitorable {
return events.clone();
}
/**
* Return the first event of the given type.
* @param type
* @return
*/
public FlightEvent getFirstEvent(FlightEvent.Type type) {
for (FlightEvent e : events) {
if (e.getType() == type) {
return e;
}
}
return null;
}
/**
* Return the last event of the given type.
* @param type
* @return
*/
public FlightEvent getLastEvent(FlightEvent.Type type) {
FlightEvent retval = null;
for (FlightEvent e : events) {
if (e.getType() == type) {
retval = e;
}
}
return retval;
}
/**
* Make this FlightDataBranch immutable. Any calls to the set methods that would

View File

@ -268,7 +268,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
this.simulation = sim;
}
public Simulation getSimulation(){
public Simulation getSimulation() {
return this.simulation;
}
@ -291,7 +291,12 @@ public class SimulationConditions implements Monitorable, Cloneable {
public SimulationConditions clone() {
try {
// TODO: HIGH: Deep clone models
return (SimulationConditions) super.clone();
SimulationConditions clone = (SimulationConditions) super.clone();
clone.simulationListeners = new ArrayList<SimulationListener>(this.simulationListeners.size());
for (SimulationListener listener : this.simulationListeners) {
clone.simulationListeners.add(listener.clone());
}
return clone;
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}

View File

@ -27,10 +27,6 @@ import net.sf.openrocket.util.WorldCoordinate;
*/
public class SimulationStatus implements Monitorable {
/*
* NOTE! All fields must be added to copyFrom() method!!
*/
private SimulationConditions simulationConditions;
private Configuration configuration;
private MotorInstanceConfiguration motorConfiguration;
@ -83,13 +79,15 @@ public class SimulationStatus implements Monitorable {
/** Available for special purposes by the listeners. */
private final Map<String, Object> extraData = new HashMap<String, Object>();
double maxAlt = Double.NEGATIVE_INFINITY;
double maxAltTime = 0;
private int modID = 0;
private int modIDadd = 0;
public SimulationStatus( Configuration configuration,
public SimulationStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions ) {
SimulationConditions simulationConditions) {
this.simulationConditions = simulationConditions;
this.configuration = configuration;
@ -163,7 +161,7 @@ public class SimulationStatus implements Monitorable {
*
* @param orig the object from which to copy
*/
public SimulationStatus( SimulationStatus orig ) {
public SimulationStatus(SimulationStatus orig) {
this.simulationConditions = orig.simulationConditions.clone();
this.configuration = orig.configuration.clone();
this.motorConfiguration = orig.motorConfiguration.clone();
@ -292,7 +290,7 @@ public class SimulationStatus implements Monitorable {
}
public boolean addBurntOutMotor( MotorId motor ) {
public boolean addBurntOutMotor(MotorId motor) {
return motorBurntOut.add(motor);
}
@ -384,7 +382,7 @@ public class SimulationStatus implements Monitorable {
}
public void setTumbling( boolean tumbling ) {
public void setTumbling(boolean tumbling) {
this.tumbling = tumbling;
this.modID++;
}
@ -393,6 +391,24 @@ public class SimulationStatus implements Monitorable {
return tumbling;
}
public double getMaxAlt() {
return maxAlt;
}
public void setMaxAlt(double maxAlt) {
this.maxAlt = maxAlt;
this.modID++;
}
public double getMaxAltTime() {
return maxAltTime;
}
public void setMaxAltTime(double maxAltTime) {
this.maxAltTime = maxAltTime;
this.modID++;
}
public Set<RecoveryDevice> getDeployedRecoveryDevices() {
return deployedRecoveryDevices;
}

View File

@ -72,7 +72,7 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
* Return an array of any flight data types this listener creates.
*/
@Override
public FlightDataType[] getFlightDataTypes(){
public FlightDataType[] getFlightDataTypes() {
return new FlightDataType[] {};
}
@ -183,4 +183,9 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
return null;
}
@Override
public AbstractSimulationListener clone() throws CloneNotSupportedException {
return (AbstractSimulationListener) super.clone();
}
}

View File

@ -4,9 +4,13 @@ import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
public interface SimulationListener {
/**
* Listen to simulation events and possibly take action.
*
* If the implementation maintains any state, it should be properly cloned.
*
*/
public interface SimulationListener extends Cloneable {
/**
* Get the name of this simulation listener. Ideally this should be localized, as
@ -83,5 +87,5 @@ public interface SimulationListener {
*/
public FlightDataType[] getFlightDataTypes();
public SimulationListener clone() throws CloneNotSupportedException;
}

View File

@ -0,0 +1,38 @@
package net.sf.openrocket.simulation.listeners.system;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
/**
* Simulation listener which ignores recovery deployment events and ends the simulation
* when apogee is reached.
*
* @author kevin
*
*/
public class OptimumCoastListener extends AbstractSimulationListener {
public static final OptimumCoastListener INSTANCE = new OptimumCoastListener();
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
if (event.getType() == FlightEvent.Type.APOGEE) {
status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime()));
}
return true;
}
@Override
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
return false;
}
@Override
public boolean isSystemListener() {
return true;
}
}

View File

@ -6,6 +6,7 @@ import javax.swing.table.TableColumnModel;
public abstract class Column {
private final String name;
private final String toolTip;
/**
* Create a new column with specified name. Additionally, the {@link #getValueAt(int)}
@ -15,6 +16,17 @@ public abstract class Column {
*/
public Column(String name) {
this.name = name;
this.toolTip = null;
}
/**
* Create a new column with specified name and toolTip.
*
*
*/
public Column(String name, String toolTip ) {
this.name = name;
this.toolTip = toolTip;
}
/**
@ -88,4 +100,8 @@ public abstract class Column {
return null;
}
public String getToolTip() {
return toolTip;
}
}

View File

@ -0,0 +1,28 @@
package net.sf.openrocket.gui.adaptors;
import java.awt.event.MouseEvent;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;
public class ColumnTable extends JTable {
public ColumnTable( ColumnTableModel model ) {
super(model);
}
@Override
protected JTableHeader createDefaultTableHeader() {
return new JTableHeader( columnModel ) {
public String getToolTipText(MouseEvent e) {
String tip = null;
java.awt.Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
tip = ((ColumnTableModel) getModel()).getColumn(realIndex).getToolTip();
return tip;
}
};
}
}

View File

@ -15,6 +15,11 @@ public abstract class ValueColumn extends Column {
this.unit = unit;
}
public ValueColumn( String name, String toolTip, UnitGroup unit ) {
super( name, toolTip );
this.unit = unit;
}
@Override
public Object getValueAt(int row) {
Double d = valueAt(row);

View File

@ -43,6 +43,7 @@ import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.FlightConfigurationModel;
@ -243,7 +244,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
}
};
table = new JTable(cpTableModel);
table = new ColumnTable(cpTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);

View File

@ -40,6 +40,7 @@ import javax.swing.table.TableRowSorter;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.components.SelectableLabel;
import net.sf.openrocket.gui.util.GUIUtil;
@ -266,7 +267,7 @@ public class DebugLogDialog extends JDialog {
}
};
table = new JTable(model);
table = new ColumnTable(model);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);

View File

@ -23,6 +23,7 @@ import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.database.Database;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
@ -108,7 +109,7 @@ public class MaterialEditPanel extends JPanel {
}
};
table = new JTable(model);
table = new ColumnTable(model);
model.setColumnWidths(table.getColumnModel());
table.setAutoCreateRowSorter(true);
table.setDefaultRenderer(Object.class, new MaterialCellRenderer());

View File

@ -35,6 +35,7 @@ import net.sf.openrocket.document.events.DocumentChangeListener;
import net.sf.openrocket.document.events.SimulationChangeEvent;
import net.sf.openrocket.formatting.RocketDescriptor;
import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.adaptors.ColumnTableRowSorter;
import net.sf.openrocket.gui.adaptors.ValueColumn;
@ -48,6 +49,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.unit.UnitGroup;
@ -184,12 +186,12 @@ public class SimulationPanel extends JPanel {
int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
new Object[] {
//// Delete the selected simulations?
trans.get("simpanel.dlg.lbl.DeleteSim1"),
//// <html><i>This operation cannot be undone.</i>
trans.get("simpanel.dlg.lbl.DeleteSim2"),
"",
panel },
//// Delete the selected simulations?
trans.get("simpanel.dlg.lbl.DeleteSim1"),
//// <html><i>This operation cannot be undone.</i>
trans.get("simpanel.dlg.lbl.DeleteSim2"),
"",
panel },
//// Delete simulations
trans.get("simpanel.dlg.lbl.DeleteSim3"),
JOptionPane.OK_CANCEL_OPTION,
@ -389,6 +391,27 @@ public class SimulationPanel extends JPanel {
}
},
//// Deployment Time from Apogee
new ValueColumn(trans.get("simpanel.col.OptimumCoastTime"),
trans.get("simpanel.col.OptimumCoastTime.ttip"),
UnitGroup.UNITS_SHORT_TIME) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null || data.getBranchCount() == 0)
return null;
double val = data.getBranch(0).getOptimumDelay();
if ( Double.isNaN(val) ) {
return null;
}
return val;
}
},
//// Maximum velocity
new ValueColumn(trans.get("simpanel.col.Maxvelocity"), UnitGroup.UNITS_VELOCITY) {
@Override
@ -465,15 +488,15 @@ public class SimulationPanel extends JPanel {
}
) {
@Override
public int getRowCount() {
return document.getSimulationCount();
}
};
@Override
public int getRowCount() {
return document.getSimulationCount();
}
};
// Override processKeyBinding so that the JTable does not catch
// key bindings used in menu accelerators
simulationTable = new JTable(simulationTableModel) {
simulationTable = new ColumnTable(simulationTableModel) {
@Override
protected boolean processKeyBinding(KeyStroke ks,
KeyEvent e,

View File

@ -125,6 +125,7 @@ public class DesignReport {
private static final String SIZE = "Size";
private static final String ALTITUDE = "Altitude";
private static final String FLIGHT_TIME = "Flight Time";
private static final String OPTIMUM_DELAY = "Optimum Delay";
private static final String TIME_TO_APOGEE = "Time to Apogee";
private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
private static final String MAX_VELOCITY = "Max Velocity";
@ -468,6 +469,9 @@ public class DesignReport {
labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
labelTable.addCell(ITextHelper.createCell(OPTIMUM_DELAY, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getBranch(0).getOptimumDelay()), 2, 2));
labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));