Merge pull request #521 from JoePfeiffer/fix-361 -- Extend simulation until last motor burns out

Fix 361
This commit is contained in:
Daniel Williams 2020-02-14 21:58:10 -05:00 committed by GitHub
commit 64514ed514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 183 additions and 23 deletions

View File

@ -1693,7 +1693,7 @@ Warning.SUPERSONIC = Body calculations may not be entirely accurate at supersoni
Warning.RECOVERY_LAUNCH_ROD = Recovery device device deployed while on the launch guide.
Warning.RECOVERY_HIGH_SPEED = Recovery device deployment at high speed
Warning.TUMBLE_UNDER_THRUST = Stage began to tumble under thrust.
Warning.EVENT_AFTER_LANDING = Flight Event occurred after landing:
! Scale dialog
ScaleDialog.lbl.scaleRocket = Entire rocket

View File

@ -3,6 +3,7 @@ package net.sf.openrocket.aerodynamics;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.unit.UnitGroup;
public abstract class Warning {
@ -123,6 +124,41 @@ public abstract class Warning {
return false;
}
}
/**
* A <code>Warning</code> indicating flight events occurred after ground hit
*
*/
public static class EventAfterLanding extends Warning {
private FlightEvent event;
/**
* Sole constructor. The argument is an event which has occurred after landing
*
* @param event the event that caused this warning
*/
public EventAfterLanding(FlightEvent _event) {
this.event = _event;
}
// I want a warning on every event that occurs after we land,
// so severity of problem is clear to the user
@Override
public boolean equals(Object o) {
return false;
}
@Override
public String toString() {
return trans.get("Warning.EVENT_AFTER_LANDING") + event.getType();
}
@Override
public boolean replaceBy(Warning other) {
return false;
}
}
public static class MissingMotor extends Warning {
@ -350,5 +386,7 @@ public abstract class Warning {
public static final Warning RECOVERY_LAUNCH_ROD = new Other(trans.get("Warning.RECOVERY_LAUNCH_ROD"));
public static final Warning TUMBLE_UNDER_THRUST = new Other(trans.get("Warning.TUMBLE_UNDER_THRUST"));
public static final Warning EVENT_AFTER_LANDING = new Other(trans.get("Warning.EVENT_AFTER_LANDING"));
}

View File

@ -35,9 +35,10 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private static final Logger log = LoggerFactory.getLogger(BasicEventSimulationEngine.class);
// TODO: MEDIUM: Allow selecting steppers
private SimulationStepper flightStepper = new RK4SimulationStepper();
private SimulationStepper flightStepper = new RK4SimulationStepper();
private SimulationStepper landingStepper = new BasicLandingStepper();
private SimulationStepper tumbleStepper = new BasicTumbleStepper();
private SimulationStepper tumbleStepper = new BasicTumbleStepper();
private SimulationStepper groundStepper = new GroundStepper();
// Constant holding 20 degrees in radians. This is the AOA condition
// necessary to transition to tumbling.
@ -107,8 +108,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private FlightDataBranch simulateLoop() {
// Initialize the simulation
currentStepper = flightStepper;
// Initialize the simulation. We'll use the flight stepper unless we're already on the ground
if (currentStatus.isLanded())
currentStepper = groundStepper;
else
currentStepper = flightStepper;
currentStatus = currentStepper.initialize(currentStatus);
// Get originating position (in case listener has modified launch position)
@ -138,16 +143,16 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for NaN values in the simulation status
checkNaN();
// Add altitude event
addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, currentStatus.getSimulationTime(),
currentStatus.getConfiguration().getRocket(),
new Pair<Double, Double>(oldAlt, currentStatus.getRocketPosition().z)));
// If we haven't hit the ground, add altitude event
if (!currentStatus.isLanded())
addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, currentStatus.getSimulationTime(),
currentStatus.getConfiguration().getRocket(),
new Pair<Double, Double>(oldAlt, currentStatus.getRocketPosition().z)));
if (currentStatus.getRocketPosition().z > currentStatus.getMaxAlt()) {
currentStatus.setMaxAlt(currentStatus.getRocketPosition().z);
}
// Position relative to start location
Coordinate relativePosition = currentStatus.getRocketPosition().sub(origin);
@ -167,10 +172,10 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} else {
// Check ground hit after liftoff
if (currentStatus.getRocketPosition().z < 0) {
currentStatus.setRocketPosition(currentStatus.getRocketPosition().setZ(0));
if ((currentStatus.getRocketPosition().z < 0) && !currentStatus.isLanded()) {
addEvent(new FlightEvent(FlightEvent.Type.GROUND_HIT, currentStatus.getSimulationTime()));
addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime()));
// addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime()));
}
}
@ -228,7 +233,10 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
}
// If I'm on the ground and have no events in the queue, I'm done
if (currentStatus.isLanded() && currentStatus.getEventQueue().isEmpty())
addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime()));
}
} catch (SimulationException e) {
@ -254,6 +262,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
for (event = nextEvent(); event != null; event = nextEvent()) {
log.trace("Obtained event from queue: " + event.toString());
log.trace("Remaining EventQueue = " + currentStatus.getEventQueue().toString());
// If I get an event other than ALTITUDE and SIMULATION_END after I'm on the ground, there's a problem
if (currentStatus.isLanded() &&
(event.getType() != FlightEvent.Type.ALTITUDE) &&
(event.getType() != FlightEvent.Type.SIMULATION_END))
currentStatus.getWarnings().add(new Warning.EventAfterLanding(event));
// Check for motor ignition events, add ignition events to queue
for (MotorClusterState state : currentStatus.getActiveMotors() ){
@ -326,7 +340,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
event.getTime() + Math.max(0.001, deployConfig.getDeployDelay()), c));
}
}
// Handle event
log.trace("Handling event " + event);
switch (event.getType()) {
@ -391,7 +405,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
//log.debug( " adding EJECTION_CHARGE event for motor "+motorState.getMotor().getDesignation()+" on stage "+stage.getStageNumber()+": "+stage.getName());
log.debug( " detected Motor Burnout for motor "+motorState.getMotor().getDesignation()+"@ "+event.getTime()+" on stage "+stage.getStageNumber()+": "+stage.getName());
double delay = motorState.getEjectionDelay();
if ( motorState.hasEjectionCharge() ){
addEvent(new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, currentStatus.getSimulationTime() + delay,
@ -478,15 +491,23 @@ public class BasicEventSimulationEngine implements SimulationEngine {
currentStatus.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude());
currentStatus.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee());
}
this.currentStepper = this.landingStepper;
this.currentStatus = currentStepper.initialize(currentStatus);
// switch to landing stepper (unless we're already on the ground)
if (!currentStatus.isLanded()) {
currentStepper = landingStepper;
currentStatus = currentStepper.initialize(currentStatus);
}
currentStatus.getFlightData().addEvent(event);
}
break;
case GROUND_HIT:
currentStatus.setLanded(true);
currentStepper = groundStepper;
currentStatus = currentStepper.initialize(currentStatus);
currentStatus.getFlightData().addEvent(event);
break;
@ -500,8 +521,10 @@ public class BasicEventSimulationEngine implements SimulationEngine {
break;
case TUMBLE:
this.currentStepper = this.tumbleStepper;
this.currentStatus = currentStepper.initialize(currentStatus);
if (!currentStatus.isLanded()) {
currentStepper = tumbleStepper;
currentStatus = currentStepper.initialize(currentStatus);
}
currentStatus.getFlightData().addEvent(event);
break;
}
@ -538,7 +561,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
/**
* Return the next flight event to handle, or null if no more events should be handled.
* This method jumps the simulation time forward in case no motors have been ignited.
* This method jumps the simulation time forward in case no motors have been ignited
* The flight event is removed from the event queue.
*
* @return the flight event to handle, or null
@ -550,7 +573,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
return null;
// Jump to event if no motors have been ignited
if (!currentStatus.isMotorIgnited() && event.getTime() > currentStatus.getSimulationTime()) {
if (!currentStatus.isMotorIgnited() &&
event.getTime() > currentStatus.getSimulationTime()) {
currentStatus.setSimulationTime(event.getTime());
}
if (event.getTime() <= currentStatus.getSimulationTime()) {

View File

@ -0,0 +1,85 @@
package net.sf.openrocket.simulation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Coordinate;
public class GroundStepper extends AbstractSimulationStepper {
private static final Logger log = LoggerFactory.getLogger(GroundStepper.class);
@Override
public SimulationStatus initialize(SimulationStatus original) {
log.trace("initializing GroundStepper");
SimulationStatus status = new SimulationStatus(original);
return status;
}
@Override
public void step(SimulationStatus status, double timeStep) throws SimulationException {
log.trace("step: position=" + status.getRocketPosition() + ", velocity=" + status.getRocketVelocity());
status.setRocketVelocity(Coordinate.ZERO);
status.setRocketRotationVelocity(Coordinate.ZERO);
status.setRocketPosition(status.getRocketPosition().setZ(0));
// Store data
FlightDataBranch data = status.getFlightData();
boolean extra = status.getSimulationConditions().isCalculateExtras();
data.addPoint();
data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
if (extra) {
data.setValue(FlightDataType.TYPE_POSITION_XY,
MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
data.setValue(FlightDataType.TYPE_VELOCITY_XY,
MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
data.setValue(FlightDataType.TYPE_ACCELERATION_XY, 0.0);
data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, 0.0);
data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Double.POSITIVE_INFINITY);
}
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
data.setValue(FlightDataType.TYPE_GRAVITY, modelGravity(status));
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, 0.0);
data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
data.setValue(FlightDataType.TYPE_ACCELERATION_Z, 0.0);
data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, 0.0);
data.setValue(FlightDataType.TYPE_MACH_NUMBER, 0.0);
data.setValue(FlightDataType.TYPE_MASS, calculateStructureMass(status).getMass());
data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, 0.0); // Is this a reasonable assumption? Probably.
data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0.0);
data.setValue(FlightDataType.TYPE_DRAG_FORCE, 0.0);
data.setValue(FlightDataType.TYPE_WIND_VELOCITY, modelWindVelocity(status).length());
AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature());
data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure());
data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed());
data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep);
data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
(System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
status.setSimulationTime(status.getSimulationTime() + timeStep);
}
}

View File

@ -70,6 +70,9 @@ public class SimulationStatus implements Monitorable {
/** Set to true to indicate the rocket is tumbling. */
private boolean tumbling = false;
/** Set to true to indicate rocket has landed */
private boolean landed = false;
/** Contains a list of deployed recovery devices. */
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
@ -175,6 +178,7 @@ public class SimulationStatus implements Monitorable {
this.launchRodCleared = orig.launchRodCleared;
this.apogeeReached = orig.apogeeReached;
this.tumbling = orig.tumbling;
this.landed = orig.landed;
this.configuration.copyStages(orig.configuration);
@ -392,6 +396,15 @@ public class SimulationStatus implements Monitorable {
public boolean isTumbling() {
return tumbling;
}
public void setLanded(boolean landed) {
this.landed = landed;
this.modID++;
}
public boolean isLanded() {
return landed;
}
public double getMaxAlt() {
return maxAlt;