[Bugfix] Fixed Simulations bugs - Sim should be operational now.

Motors are now cloned for each new simulation (including background and foreground runs)
    -> motors are now cloned within the configuration clone method.
    -> MotorInstance.reset() resets instance to launch state
Removed spurious negative time-step check in ThrustCurveMotorInstance:154
    -> was triggered during normal simulation operations.
Added MotorInstance time updates to AbstractSimulationStepper:
    -> moved from MotorInstanceConfiguration.step(...)
Misc variable name changes to be more descriptive
This commit is contained in:
Daniel_M_Williams 2015-12-06 12:09:29 -05:00
parent 65ddd1b2a2
commit 1988ee4266
11 changed files with 118 additions and 145 deletions

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.document;
import java.util.Collection;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
@ -281,10 +282,9 @@ public class Simulation implements ChangeSource, Cloneable {
}
FlightConfiguration config = rocket.getFlightConfiguration(options.getId());
List<MotorInstance> motorList = config.getActiveMotors();
//Make sure this simulation has motors.
if (0 == motorList.size()){
if ( ! config.hasMotors() ){
status = Status.CANT_RUN;
}
@ -337,14 +337,11 @@ public class Simulation implements ChangeSource, Cloneable {
// Set simulated info after simulation, will not be set in case of exception
simulatedConditions = options.clone();
final FlightConfiguration configuration = this.rocket.getFlightConfiguration( options.getId());
simulatedConfigurationDescription = descriptor.format(configuration.getRocket(), configuration.getFlightConfigurationID());
simulatedConfigurationDescription = descriptor.format( this.rocket, options.getId());
simulatedRocketID = rocket.getFunctionalModID();
status = Status.UPTODATE;
fireChangeEvent();
configuration.release();
} finally {
mutex.unlock("simulate");
}

View File

@ -300,7 +300,7 @@ public abstract class BaseHandler<C extends RocketComponent> extends AbstractEle
*
* @return the Method instance, or null
*/
private static Method getMethod(RocketComponent component, String name, Class[] args) {
private static Method getMethod(RocketComponent component, String name, Class<?>[] args) {
Method method = null;
try {
method = component.getClass().getMethod(name, args);

View File

@ -155,7 +155,7 @@ public abstract class MotorInstance implements FlightConfigurableParameter<Motor
* @param acceleration the average acceleration during the step.
* @param cond the average atmospheric conditions during the step.
*/
public abstract void step(double time, double acceleration, AtmosphericConditions cond);
public abstract void step(double newTime, double acceleration, AtmosphericConditions cond);
/**
@ -254,6 +254,8 @@ public abstract class MotorInstance implements FlightConfigurableParameter<Motor
((StateChangeListener) l).stateChanged(event);
}
}
public String toDebug(){ return toString();}
@Override
public String toString(){
@ -263,5 +265,9 @@ public abstract class MotorInstance implements FlightConfigurableParameter<Motor
public int getModID() {
return modID;
}
public void reset() {
}
}

View File

@ -41,17 +41,12 @@ public class ThrustCurveMotorInstance extends MotorInstance {
public ThrustCurveMotorInstance(final ThrustCurveMotor source) {
//log.debug( Creating motor instance of " + ThrustCurveMotor.this);
timeIndex = 0;
prevTime = 0;
instThrust = 0;
stepThrust = 0;
instCG = source.getLaunchCG();
stepCG = source.getLaunchCG();
this.motor = source;
this.reset();
unitRotationalInertia = Inertia.filledCylinderRotational(source.getDiameter() / 2);
unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(source.getDiameter() / 2, source.getLength());
this.motor = source;
this.id = MotorInstanceId.ERROR_ID;
}
@ -153,12 +148,6 @@ public class ThrustCurveMotorInstance extends MotorInstance {
@Override
public void step(double nextTime, double acceleration, AtmosphericConditions cond) {
if (!(nextTime >= prevTime)) {
// Also catches NaN
throw new IllegalArgumentException("Stepping backwards in time, current=" +
prevTime + " new=" + nextTime);
}
if (MathUtil.equals(prevTime, nextTime)) {
return;
}
@ -241,11 +230,37 @@ public class ThrustCurveMotorInstance extends MotorInstance {
clone.ignitionDelay = this.ignitionDelay;
clone.ejectionDelay = this.ejectionDelay;
clone.position = this.position;
this.ignitionTime = Double.POSITIVE_INFINITY;
clone.ignitionTime = Double.POSITIVE_INFINITY;
//clone.ignitionTime = this.ignitionTime;
return clone;
}
@Override
public void reset(){
timeIndex = 0;
prevTime = 0;
instThrust = 0;
stepThrust = 0;
instCG = motor.getLaunchCG();
stepCG = instCG;
}
@Override
public String toDebug(){
String prefix = " ";
StringBuilder sb = new StringBuilder();
final RocketComponent mountComp = (RocketComponent)this.mount;
sb.append(String.format("%sMotor= %s(%s)(in %s)\n", prefix, this.motor.getDesignation(), this.id.toShortKey(), mountComp.getName()));
sb.append(String.format("%s Ignite: %s at %+f\n",prefix, this.ignitionEvent.name, this.ignitionDelay));
//sb.append(String.format("%s Eject at: %+f\n",prefix, this.ejectionDelay));
sb.append(String.format("%s L:%f W:%f @:%f mm\n",prefix, motor.getLength(), motor.getDiameter(), this.position.multiply(1000).x ));
sb.append(String.format("%s currentTimem: %f\n", prefix, this.prevTime));
sb.append("\n");
return sb.toString();
}
@Override
public String toString(){
return this.id.toString();

View File

@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@ -14,7 +13,6 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.util.ArrayList;
@ -68,8 +66,6 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
private int modID = 0;
private boolean debug = false;
/**
* Create a new configuration with the specified <code>Rocket</code>.
*
@ -91,10 +87,6 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
rocket.addComponentChangeListener(this);
}
public void enableDebugging(){
this.debug=true;
}
public Rocket getRocket() {
return rocket;
}
@ -305,9 +297,6 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
private void updateStages() {
if( debug){
System.err.println("updating config stages");
}
if (this.rocket.getStageCount() == this.stages.size()) {
// no changes needed
return;
@ -364,7 +353,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
// DEBUG / DEVEL
public String toMotorDetail(){
StringBuilder buff = new StringBuilder();
buff.append(String.format("\nDumping %2d Motors for configuration %s: \n", this.motors.size(), this.fcid.toShortKey()));
buff.append(String.format("\nDumping %2d Motors for configuration %s: \n", this.motors.size(), this));
for( MotorInstance curMotor : this.motors.values() ){
if( curMotor.isEmpty() ){
buff.append( String.format( " ..[%8s] <empty> \n", curMotor.getID().toShortKey()));
@ -490,21 +479,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
return (0 < motors.size());
}
/**
* Step all of the motor instances to the specified time minus their ignition time.
* @param time the "global" time
*/
public void step(double time, double acceleration, AtmosphericConditions cond) {
for (MotorInstance inst : motors.values()) {
double t = time - inst.getIgnitionTime();
if (t >= 0) {
inst.step(t, acceleration, cond);
}
}
modID++;
}
public List<MotorInstance> getActiveMotors() {
public Collection<MotorInstance> getActiveMotors() {
List<MotorInstance> activeList = new ArrayList<MotorInstance>();
for( MotorInstance inst : this.motors.values() ){
if( inst.isActive() ){
@ -516,47 +491,35 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
public void updateMotors() {
if( debug){
System.err.println("updating config motors");
}
this.motors.clear();
for ( RocketComponent comp : getActiveComponents() ){
if (( comp instanceof MotorMount )&&( ((MotorMount)comp).isMotorMount())){
MotorMount mount = (MotorMount)comp;
MotorInstance inst = mount.getMotorInstance( fcid);
if( inst.isEmpty()){
for ( RocketComponent compMount : getActiveComponents() ){
if (( compMount instanceof MotorMount )&&( ((MotorMount)compMount).isMotorMount())){
MotorMount mount = (MotorMount)compMount;
MotorInstance sourceInstance = mount.getMotorInstance( fcid);
if( sourceInstance.isEmpty()){
continue;
}
// this merely accounts for instancing of *this* component:
// int instancCount = comp.getInstanceCount();
// this includes *all* the instancing between here and the rocket root.
Coordinate[] instanceLocations= comp.getLocations();
Coordinate[] instanceLocations= compMount.getLocations();
if( debug){
System.err.println(String.format(",,,,,,,, %s (%s)",
inst.getMotor().getDigest(), inst.getID() ));
}
sourceInstance.reset();
int instanceNumber = 1;
final int instanceCount = instanceLocations.length;
//final int instanceCount = instanceLocations.length;
for ( Coordinate curMountLocation : instanceLocations ){
MotorInstance curInstance = inst.clone();
curInstance.setID( new MotorInstanceId( comp.getName(), instanceNumber) );
// motor location w/in mount: parent.refpoint -> motor.refpoint
Coordinate curMotorOffset = curInstance.getOffset();
curInstance.setPosition( curMountLocation.add(curMotorOffset) );
this.motors.put( curInstance.getID(), curInstance);
// vvvv DEVEL vvvv
if(debug){
System.err.println(String.format(",,,,,,,, [%2d/%2d]: %s. (%s)",
instanceNumber, instanceCount, curInstance.getMotor().getDigest(), curInstance));
}
// ^^^^ DEVEL ^^^^
instanceNumber ++;
MotorInstance cloneInstance = sourceInstance.clone();
cloneInstance.setID( new MotorInstanceId( compMount.getName(), instanceNumber) );
// motor location w/in mount: parent.refpoint -> motor.refpoint
Coordinate curMotorOffset = cloneInstance.getOffset();
cloneInstance.setPosition( curMountLocation.add(curMotorOffset) );
this.motors.put( cloneInstance.getID(), cloneInstance);
instanceNumber ++;
}
}
@ -629,7 +592,9 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
clone.setName(this.fcid.toShortKey()+" - clone");
clone.listenerList = new ArrayList<EventListener>();
clone.stages.putAll( (Map<Integer, StageFlags>) this.stages);
clone.motors.putAll( (Map<MotorInstanceId, MotorInstance>) this.motors);
for( MotorInstance mi : this.motors.values()){
clone.motors.put( mi.getID(), mi.clone());
}
clone.cachedBounds = this.cachedBounds.clone();
clone.modID = this.modID;
clone.boundsModID = -1;
@ -662,5 +627,5 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
this.isNamed = true;
this.configurationName = newName;
}
}

View File

@ -55,7 +55,7 @@ public enum IgnitionEvent {
private static final Translator trans = Application.getTranslator();
public final String name;
private final String key;
private final String translationKey;
protected String description=null;
//public static final IgnitionEvent[] events = {AUTOMATIC, LAUNCH, EJECTION_CHARGE, BURNOUT, NEVER};
@ -67,7 +67,7 @@ public enum IgnitionEvent {
private IgnitionEvent(final String _name, final String _key) {
this.name = _name;
this.key = _key;
this.translationKey = _key;
}
public boolean equals( final String content){
@ -82,7 +82,7 @@ public enum IgnitionEvent {
@Override
public String toString() {
if( null == this.description ){
this.description = trans.get(this.key);
this.description = trans.get(this.translationKey);
}
return this.description;
}

View File

@ -1,6 +1,6 @@
package net.sf.openrocket.simulation;
import java.util.List;
import java.util.Collection;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
@ -170,22 +170,17 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
return thrust;
}
FlightConfiguration configuration = status.getConfiguration();
//MotorInstanceConfiguration mic = status.getMotorConfiguration();
// Iterate over the motors and calculate combined thrust
//if (!stepMotors) {
// mic = mic.clone();
//}
//mic.step(status.getSimulationTime() + timestep, acceleration, atmosphericConditions);
// now this is a valid reason for the MotorConfiguration :P
configuration.step(status.getSimulationTime() + timestep, acceleration, atmosphericConditions);
thrust = 0;
//?? needs to instance the motors...
List<MotorInstance> activeMotors = configuration.getActiveMotors();
for (MotorInstance currentMotorInstance : activeMotors) {
final double currentTime = status.getSimulationTime() + timestep;
Collection<MotorInstance> activeMotorList = status.getConfiguration().getActiveMotors();
for (MotorInstance currentMotorInstance : activeMotorList ) {
// old: transplanted from MotorInstanceConfiguration
double instanceTime = currentTime - currentMotorInstance.getIgnitionTime();
if (instanceTime >= 0) {
currentMotorInstance.step(instanceTime, acceleration, atmosphericConditions);
}
// old: from here
thrust += currentMotorInstance.getThrust();
}

View File

@ -1,8 +1,8 @@
package net.sf.openrocket.simulation;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -56,15 +56,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private FlightConfigurationID fcid;
// old: protected Stack<SimulationStatus> stages = new Stack<SimulationStatus>();
// this variable was class member stack, but parallel staging breaks metaphor:
// parallel stages may ignite before OR after their 'inner' stages
// this is just a list of simulation branches to
Deque<SimulationStatus> toSimulate = new ArrayDeque<SimulationStatus>();
@Override
public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException {
@ -72,21 +66,17 @@ public class BasicEventSimulationEngine implements SimulationEngine {
FlightData flightData = new FlightData();
// Set up rocket configuration
this.fcid = simulationConditions.getConfigurationID();
FlightConfiguration configuration = simulationConditions.getRocket().getFlightConfiguration( this.fcid);
List<MotorInstance> activeMotors = configuration.getActiveMotors();
if ( activeMotors.isEmpty() ) {
final String errorMessage = trans.get("BasicEventSimulationEngine.error.noMotorsDefined");
log.info(errorMessage);
throw new MotorIgnitionException(errorMessage);
this.fcid = simulationConditions.getFlightConfigurationID();
FlightConfiguration simulationConfig = simulationConditions.getRocket().getFlightConfiguration( this.fcid).clone();
if ( ! simulationConfig.hasMotors() ) {
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined"));
}
currentStatus = new SimulationStatus(configuration, simulationConditions);
currentStatus = new SimulationStatus(simulationConfig, simulationConditions);
currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket()));
{
// main simulation branch
final String branchName = configuration.getRocket().getTopmostStage().getName();
final String branchName = simulationConfig.getRocket().getTopmostStage().getName();
currentStatus.setFlightData(new FlightDataBranch( branchName, FlightDataType.TYPE_TIME));
}
toSimulate.add(currentStatus);
@ -103,19 +93,18 @@ public class BasicEventSimulationEngine implements SimulationEngine {
flightData.addBranch(dataBranch);
flightData.getWarningSet().addAll(currentStatus.getWarnings());
log.info(String.format("<<Finished simulating branch: %s at:%s",
currentStatus.getFlightData().getBranchName(),
currentStatus.getFlightData().getLast(FlightDataType.TYPE_TIME)));
log.info(String.format("<<Finished simulating branch: %s curTime:%s finTime:%s",
dataBranch.getBranchName(),
currentStatus.getSimulationTime(),
dataBranch.getLast(FlightDataType.TYPE_TIME)));
}while( ! toSimulate.isEmpty());
SimulationListenerHelper.fireEndSimulation(currentStatus, null);
configuration.release();
if (!flightData.getWarningSet().isEmpty()) {
log.info("Warnings at the end of simulation: " + flightData.getWarningSet());
}
simulationConfig.release();
return flightData;
}
@ -130,12 +119,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Coordinate originVelocity = currentStatus.getRocketVelocity();
try {
log.info(String.format(" >> Starting simulate loop."));
// Start the simulation
while (handleEvents()) {
log.info(String.format(" >> Events Handled."));
// Take the step
double oldAlt = currentStatus.getRocketPosition().z;
@ -286,7 +272,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
log.trace("BasicEventSimulationEngine: Handling event " + event);
}
log.trace(String.format(" >> about to ignite motors events"));
if (event.getType() == FlightEvent.Type.IGNITION) {
MotorMount mount = (MotorMount) event.getSource();
MotorInstanceId motorId = (MotorInstanceId) event.getData();
@ -295,7 +280,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
continue;
}
}
log.trace(String.format(" >> about to fire motors (?) events"));
if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) {
RecoveryDevice device = (RecoveryDevice) event.getSource();
if (!SimulationListenerHelper.fireRecoveryDeviceDeployment(currentStatus, device)) {
@ -303,8 +287,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
}
log.trace(String.format(" >> about to check for motors ignite events"));
// Check for motor ignition events, add ignition events to queue
for (MotorInstance motor : currentStatus.getFlightConfiguration().getActiveMotors() ){
MotorInstanceId mid = motor.getID();
@ -322,7 +304,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for stage separation event
for (AxialStage stage : currentStatus.getConfiguration().getActiveStages()) {
int stageNo = stage.getStageNumber();
if (stageNo == 0)
@ -348,8 +329,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
}
log.trace(String.format(" >> about to handle events"));
// Handle event
switch (event.getType()) {
@ -445,7 +424,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// TODO: HIGH: Check stage activeness for other events as well?
// Check whether any motor in the active stages is active anymore
List<MotorInstance> activeMotors = currentStatus.getConfiguration().getActiveMotors();
Collection<MotorInstance> activeMotors = currentStatus.getConfiguration().getActiveMotors();
for (MotorInstance curMotor : activeMotors) {
RocketComponent comp = ((RocketComponent) curMotor.getMount());
int stageNumber = comp.getStageNumber();
@ -471,6 +450,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// to determine the optimum altitude.
if (currentStatus.getSimulationConditions().isCalculateExtras() && !currentStatus.isApogeeReached()) {
FlightData coastStatus = computeCoastTime();
currentStatus.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude());
currentStatus.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee());
}
@ -580,7 +560,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
SimulationConditions conds = currentStatus.getSimulationConditions().clone();
conds.getSimulationListenerList().add(OptimumCoastListener.INSTANCE);
BasicEventSimulationEngine e = new BasicEventSimulationEngine();
// log.error(" cloned simConditions: "+conds.toString()
// +" ... "+conds.getRocket().getName()
// +" ... "+conds.getFlightConfigurationID().toShortKey());
// FlightConfigurationID dbid = conds.getFlightConfigurationID();
// FlightConfiguration cloneConfig = conds.getRocket().getFlightConfiguration( dbid );
// System.err.println(" configId: "+dbid.toShortKey());
// System.err.println(" motors detail: "+cloneConfig.toMotorDetail());
FlightData d = e.simulate(conds);
return d;
} catch (Exception e) {

View File

@ -265,6 +265,10 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
status.setRocketWorldPosition(w);
if (!(0 <= store.timestep)) {
// Also catches NaN
throw new IllegalArgumentException("Stepping backwards in time, timestep=" +store.timestep);
}
status.setSimulationTime(status.getSimulationTime() + store.timestep);
status.setPreviousTimeStep(store.timestep);

View File

@ -28,7 +28,7 @@ import net.sf.openrocket.util.WorldCoordinate;
public class SimulationConditions implements Monitorable, Cloneable {
private Rocket rocket;
private FlightConfigurationID configID= null;
private FlightConfigurationID configId= null;
private Simulation simulation; // The parent simulation
@ -116,15 +116,15 @@ public class SimulationConditions implements Monitorable, Cloneable {
public FlightConfigurationID getMotorConfigurationID() {
return configID;
return configId;
}
public FlightConfigurationID getConfigurationID() {
return configID;
public FlightConfigurationID getFlightConfigurationID() {
return configId;
}
public void setFlightConfigurationID(FlightConfigurationID _fcid) {
this.configID = _fcid;
this.configId = _fcid;
this.modID++;
}
@ -327,6 +327,9 @@ public class SimulationConditions implements Monitorable, Cloneable {
for (SimulationListener listener : this.simulationListeners) {
clone.simulationListeners.add(listener.clone());
}
clone.rocket = this.rocket; // the rocket should be read-only from this point
clone.configId = this.configId; // configIds are read-only
return clone;
} catch (CloneNotSupportedException e) {
throw new BugException(e);

View File

@ -19,6 +19,9 @@ import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
@ -35,9 +38,8 @@ import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Chars;
public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount> {
private static final long serialVersionUID = -5046535300435793744L;
private static final String NONE = trans.get("edtmotorconfdlg.tbl.None");
private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton;
@ -250,13 +252,12 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
return;
}
// this call also performs the update changes
IgnitionSelectionDialog ignitionDialog = new IgnitionSelectionDialog(
SwingUtilities.getWindowAncestor(this.flightConfigurationPanel),
fcid,
curMount);
ignitionDialog.setVisible(true);
// changes performed automatically within "new IgnitionSelectionDialog(...)"
fireTableDataChanged();
}