Merge pull request #1478 from SiboVG/issue-1477

[#1477 & #1460] Don't include inactive stages in calculations
This commit is contained in:
Joe Pfeiffer 2022-07-06 15:34:09 -06:00 committed by GitHub
commit 173b332602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 503 additions and 71 deletions

View File

@ -68,5 +68,5 @@ public interface AerodynamicCalculator extends Monitorable {
*/
public AerodynamicCalculator newInstance();
public boolean isContinuous( final Rocket rkt);
public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt);
}

View File

@ -4,6 +4,7 @@ import static net.sf.openrocket.util.MathUtil.pow2;
import java.util.*;
import net.sf.openrocket.rocketcomponent.AxialStage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -80,7 +81,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
Map<RocketComponent, AerodynamicForces> assemblyMap = new LinkedHashMap<>();
// Calculate non-axial force data
calculateForceAnalysis(conditions, configuration.getRocket(), instMap, eachMap, assemblyMap, warnings);
calculateForceAnalysis(configuration, conditions, configuration.getRocket(), instMap, eachMap, assemblyMap, warnings);
// Calculate drag coefficient data
AerodynamicForces rocketForces = assemblyMap.get(configuration.getRocket());
@ -125,7 +126,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
return finalMap;
}
private AerodynamicForces calculateForceAnalysis( FlightConditions conds,
private AerodynamicForces calculateForceAnalysis( FlightConfiguration configuration,
FlightConditions conds,
RocketComponent comp,
InstanceMap instances,
Map<RocketComponent, AerodynamicForces> eachForces,
@ -152,8 +154,12 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
}
for( RocketComponent child : comp.getChildren()) {
// Ignore inactive stages
if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) {
continue;
}
// forces particular to each component
AerodynamicForces childForces = calculateForceAnalysis(conds, child, instances, eachForces, assemblyForces, warnings);
AerodynamicForces childForces = calculateForceAnalysis(configuration, conds, child, instances, eachForces, assemblyForces, warnings);
if(null != childForces) {
aggregateForces.merge(childForces);
@ -240,7 +246,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
if (calcMap == null)
buildCalcMap(configuration);
if( ! isContinuous( configuration.getRocket() ) ){
if (!isContinuous(configuration, configuration.getRocket())){
warnings.add( Warning.DIAMETER_DISCONTINUITY);
}
@ -266,20 +272,32 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
}
@Override
public boolean isContinuous( final Rocket rkt){
return testIsContinuous( rkt);
public boolean isContinuous(FlightConfiguration configuration, final Rocket rkt){
return testIsContinuous(configuration, rkt);
}
private boolean testIsContinuous( final RocketComponent treeRoot ){
private boolean testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot ){
Queue<RocketComponent> queue = new LinkedList<>();
queue.addAll(treeRoot.getChildren());
for (RocketComponent child : treeRoot.getChildren()) {
// Ignore inactive stages
if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) {
continue;
}
queue.add(child);
}
boolean isContinuous = true;
SymmetricComponent prevComp = null;
while((isContinuous)&&( null != queue.peek())){
RocketComponent comp = queue.poll();
if( comp instanceof SymmetricComponent ){
queue.addAll( comp.getChildren());
for (RocketComponent child : comp.getChildren()) {
// Ignore inactive stages
if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) {
continue;
}
queue.add(child);
}
SymmetricComponent sym = (SymmetricComponent) comp;
if( null == prevComp){
@ -303,7 +321,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
prevComp = sym;
}else if( comp instanceof ComponentAssembly ){
isContinuous &= testIsContinuous( comp );
isContinuous &= testIsContinuous(configuration, comp);
}
}
@ -319,7 +337,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
* @param configuration Rocket configuration
* @param conditions Flight conditions taken into account
* @param map ?
* @param set Set to handle
* @param warningSet Set to handle warnings
* @return friction drag for entire rocket
*/
private double calculateFrictionCD(FlightConfiguration configuration, FlightConditions conditions,
@ -611,7 +629,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
double radius = 0;
final SymmetricComponent prevComponent = s.getPreviousSymmetricComponent();
if (prevComponent != null)
if (prevComponent != null && configuration.isComponentActive(prevComponent))
radius = prevComponent.getAftRadius();
if (radius < s.getForeRadius()) {
@ -672,7 +690,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
// its aft CD
double radius = 0;
final SymmetricComponent prevComponent = s.getPreviousSymmetricComponent();
if (prevComponent != null) {
if (prevComponent != null && configuration.isComponentActive(prevComponent)) {
radius = prevComponent.getAftRadius();
}

View File

@ -104,7 +104,7 @@ public class MassCalculator implements Monitorable {
public static RigidBody calculate( final MassCalculation.Type _type, final SimulationStatus status ){
final FlightConfiguration config = status.getConfiguration();
final double time = status.getSimulationTime();
final Collection<MotorClusterState> activeMotorList = status.getMotors();
final Collection<MotorClusterState> activeMotorList = status.getActiveMotors();
MassCalculation calculation= new MassCalculation( _type, config, time, activeMotorList, config.getRocket(), Transformation.IDENTITY, null);
calculation.calculateAssembly();

View File

@ -4,6 +4,7 @@ import java.util.Locale;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application;
@ -13,25 +14,25 @@ public enum IgnitionEvent {
//// Automatic (launch or ejection charge)
AUTOMATIC( "AUTOMATIC", "MotorMount.IgnitionEvent.AUTOMATIC"){
@Override
public boolean isActivationEvent(FlightEvent testEvent, RocketComponent targetComponent) {
public boolean isActivationEvent(FlightConfiguration config, FlightEvent testEvent, RocketComponent targetComponent) {
AxialStage targetStage = targetComponent.getStage();
if ( targetStage.isLaunchStage() ){
return LAUNCH.isActivationEvent(testEvent, targetComponent);
if (targetStage.isLaunchStage(config)) {
return LAUNCH.isActivationEvent(config, testEvent, targetComponent);
} else {
return EJECTION_CHARGE.isActivationEvent(testEvent, targetComponent);
return EJECTION_CHARGE.isActivationEvent(config, testEvent, targetComponent);
}
}
},
LAUNCH ( "LAUNCH", "MotorMount.IgnitionEvent.LAUNCH"){
@Override
public boolean isActivationEvent( FlightEvent fe, RocketComponent source){
public boolean isActivationEvent(FlightConfiguration config, FlightEvent fe, RocketComponent source){
return (fe.getType() == FlightEvent.Type.LAUNCH);
}
},
EJECTION_CHARGE ("EJECTION_CHARGE", "MotorMount.IgnitionEvent.EJECTION_CHARGE"){
@Override
public boolean isActivationEvent( FlightEvent testEvent, RocketComponent targetComponent){
public boolean isActivationEvent(FlightConfiguration config, FlightEvent testEvent, RocketComponent targetComponent){
if (testEvent.getType() != FlightEvent.Type.EJECTION_CHARGE){
return false;
}
@ -44,7 +45,7 @@ public enum IgnitionEvent {
},
BURNOUT ("BURNOUT", "MotorMount.IgnitionEvent.BURNOUT"){
@Override
public boolean isActivationEvent( FlightEvent testEvent, RocketComponent targetComponent){
public boolean isActivationEvent(FlightConfiguration config, FlightEvent testEvent, RocketComponent targetComponent){
if (testEvent.getType() != FlightEvent.Type.BURNOUT)
return false;
@ -64,7 +65,7 @@ public enum IgnitionEvent {
//public static final IgnitionEvent[] events = {AUTOMATIC, LAUNCH, EJECTION_CHARGE, BURNOUT, NEVER};
public boolean isActivationEvent( FlightEvent fe, RocketComponent source){
public boolean isActivationEvent(FlightConfiguration config, FlightEvent fe, RocketComponent source){
// default behavior. Also for the NEVER case.
return false;
}

View File

@ -67,6 +67,23 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC
public boolean isCompatible(Class<? extends RocketComponent> type) {
return BodyComponent.class.isAssignableFrom(type);
}
/**
* Returns whether the current stage is active in the currently selected configuration.
* @return true if the stage is active, false if not
*/
public boolean isStageActive() {
return getRocket().getSelectedConfiguration().isStageActive(getStageNumber());
}
/**
* Returns whether the current stage is active in the flight configuration.
* @param fc the flight configuration to check
* @return true if the stage is active, false if not
*/
public boolean isStageActive(FlightConfiguration fc) {
return fc.isStageActive(getStageNumber());
}
@Override
public void copyFlightConfiguration(FlightConfigurationId oldConfigId, FlightConfigurationId newConfigId) {
@ -113,11 +130,11 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC
/**
* returns if the object is a launch stage
* @param config the flight configuration which will check which stages are active
* @return if the object is a launch stage
*/
public boolean isLaunchStage(){
return ( this instanceof ParallelStage )
||( getRocket().getBottomCoreStage().equals(this));
public boolean isLaunchStage(FlightConfiguration config) {
return (getRocket().getBottomCoreStage(config).equals(this));
}
/**

View File

@ -60,8 +60,8 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
/* Cached data */
final protected HashMap<Integer, StageFlags> stages = new HashMap<Integer, StageFlags>();
final protected HashMap<MotorConfigurationId, MotorConfiguration> motors = new HashMap<MotorConfigurationId, MotorConfiguration>();
final protected Map<Integer, StageFlags> stages = new HashMap<Integer, StageFlags>(); // Map of stage number to StageFlags of the corresponding stage
final protected Map<MotorConfigurationId, MotorConfiguration> motors = new HashMap<MotorConfigurationId, MotorConfiguration>();
final private Collection<MotorConfiguration> activeMotors = new ArrayList<MotorConfiguration>();
final private InstanceMap activeInstances = new InstanceMap();
@ -179,31 +179,52 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
*/
public void setOnlyStage(final int stageNumber) {
_setAllStages(false);
_setStageActive(stageNumber, true);
_setStageActive(stageNumber, true, false);
updateMotors();
updateActiveInstances();
}
/**
/**
* This method flags the specified stage as requested. Other stages are unaffected.
*
*
* @param stageNumber stage number to flag
* @param _active inactive (<code>false</code>) or active (<code>true</code>)
* @param activateSubStages whether the sub-stages of the specified stage should be activated as well.
*/
private void _setStageActive(final int stageNumber, final boolean _active ) {
public void _setStageActive(final int stageNumber, final boolean _active, final boolean activateSubStages) {
if ((0 <= stageNumber) && (stages.containsKey(stageNumber))) {
stages.get(stageNumber).active = _active;
if (activateSubStages) {
// Set the active state of all the sub-stages as well.
for (AxialStage stage : rocket.getStage(stageNumber).getSubStages()) {
stages.get(stage.getStageNumber()).active = _active;
}
}
fireChangeEvent();
return;
}
log.error("error: attempt to retrieve via a bad stage number: " + stageNumber);
}
/**
* This method flags the specified stage as requested. Actives the sub-stages of the specified stage as well.
*
* @param stageNumber stage number to flag
* @param _active inactive (<code>false</code>) or active (<code>true</code>)
*/
public void _setStageActive(final int stageNumber, final boolean _active ) {
_setStageActive(stageNumber, _active, true);
}
public void toggleStage(final int stageNumber) {
if ((0 <= stageNumber) && (stages.containsKey(stageNumber))) {
StageFlags flags = stages.get(stageNumber);
flags.active = !flags.active;
// Set the active state of all the sub-stages as well.
for (AxialStage stage : rocket.getStage(stageNumber).getSubStages()) {
stages.get(stage.getStageNumber()).active = flags.active;
}
updateMotors();
updateActiveInstances();
@ -338,6 +359,18 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
return results;
}
/**
* Return all the stages in this configuration.
* @return all the stages in this configuration.
*/
public List<AxialStage> getAllStages() {
List<AxialStage> stages = new ArrayList<>();
for (StageFlags flags : this.stages.values()) {
stages.add( rocket.getStage(flags.stageNumber));
}
return stages;
}
public List<AxialStage> getActiveStages() {
List<AxialStage> activeStages = new ArrayList<>();

View File

@ -111,8 +111,8 @@ public class ParallelStage extends AxialStage implements FlightConfigurableCompo
}
@Override
public boolean isLaunchStage(){
return true;
public boolean isLaunchStage(FlightConfiguration config) {
return config.isStageActive(this.stageNumber);
}
@Override

View File

@ -197,24 +197,37 @@ public class Rocket extends ComponentAssembly {
public AxialStage getStage( final int stageNumber ) {
return this.stageMap.get( stageNumber);
}
/*
* Returns the stage at the top of the central stack
*
* @Return a reference to the topmost stage
/**
* Get the topmost stage, only taking into account active stages from the flight configuration.
* @param config flight configuration dictating which stages are active
* @return the topmost active stage, or null if there are no active stages.
*/
public AxialStage getTopmostStage(){
return (AxialStage) getChild(0);
public AxialStage getTopmostStage(FlightConfiguration config) {
if (config == null) return null;
for (int i = 0; i < getChildCount(); i++) {
if (getChild(i) instanceof AxialStage && config.isStageActive(getChild(i).getStageNumber())) {
return (AxialStage) getChild(i);
}
}
return null;
}
/*
* Returns the stage at the top of the central stack
*
* @Return a reference to the topmost stage
/**
* Get the bottommost stage, only taking into account active stages from the flight configuration.
* @param config flight configuration dictating which stages are active
* @return the bottommost active stage, or null if there are no active stages.
*/
/*package-local*/ AxialStage getBottomCoreStage(){
// get last stage that's a direct child of the rocket.
return (AxialStage) children.get( children.size()-1 );
public AxialStage getBottomCoreStage(FlightConfiguration config) {
if (config == null) return null;
for (int i = getChildCount() - 1; i >= 0; i--) {
if (getChild(i) instanceof AxialStage && config.isStageActive(getChild(i).getStageNumber())) {
return (AxialStage) getChild(i);
}
}
return null;
}
@Override
@ -735,6 +748,14 @@ public class Rocket extends ComponentAssembly {
fireComponentChangeEvent(ComponentChangeEvent.TREE_CHANGE);
return nextConfig.getFlightConfigurationID();
}
/**
* Return all the flight configurations of this rocket.
* @return all the flight configurations of this rocket.
*/
public FlightConfigurableParameterSet<FlightConfiguration> getFlightConfigurations() {
return this.configSet;
}
/**

View File

@ -1545,6 +1545,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
AxialStage stage = (AxialStage) component;
this.getRocket().forgetStage(stage);
}
// Remove sub-stages of the removed component
for (AxialStage stage : component.getSubStages()) {
this.getRocket().forgetStage(stage);
}
this.checkComponentStructure();
component.checkComponentStructure();
@ -1749,6 +1754,21 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
}
throw new IllegalStateException("getStage() called on hierarchy without an AxialStage.");
}
/**
* Returns all the stages that are a child or sub-child of this component.
* @return all the stages that are a child or sub-child of this component.
*/
public final List<AxialStage> getSubStages() {
List<AxialStage> result = new LinkedList<>();
Iterator<RocketComponent> it = iterator(false);
while (it.hasNext()) {
RocketComponent c = it.next();
if (c instanceof AxialStage)
result.add((AxialStage) c);
}
return result;
}
/**
* Return the first component assembly component that this component belongs to.

View File

@ -45,7 +45,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private final static double AOA_TUMBLE_CONDITION = Math.PI / 9.0;
// The thrust must be below this value for the transition to tumbling.
// TODO: this is an arbitrary value
// TODO HIGH: this is an arbitrary value
private final static double THRUST_TUMBLE_CONDITION = 0.01;
private SimulationStepper currentStepper;
@ -65,7 +65,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Set up rocket configuration
this.fcid = simulationConditions.getFlightConfigurationID();
FlightConfiguration simulationConfig = simulationConditions.getRocket().getFlightConfiguration( this.fcid).clone();
FlightConfiguration origConfig = simulationConditions.getRocket().getFlightConfiguration(this.fcid);
FlightConfiguration simulationConfig = origConfig.clone();
simulationConfig.copyStages(origConfig); // Clone the stage activation configuration
if ( ! simulationConfig.hasMotors() ) {
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined"));
}
@ -74,7 +76,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket()));
{
// main simulation branch
final String branchName = simulationConfig.getRocket().getTopmostStage().getName();
final String branchName = simulationConfig.getRocket().getTopmostStage(currentStatus.getConfiguration()).getName();
currentStatus.setFlightData(new FlightDataBranch( branchName, FlightDataType.TYPE_TIME));
}
toSimulate.push(currentStatus);
@ -273,9 +275,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for motor ignition events, add ignition events to queue
for (MotorClusterState state : currentStatus.getActiveMotors() ){
if( state.testForIgnition(event )){
final double simulationTime = currentStatus.getSimulationTime() ;
if (state.testForIgnition(currentStatus.getConfiguration(), event)) {
MotorClusterState sourceState = (MotorClusterState) event.getData();
double ignitionDelay = 0;
if (event.getType() == FlightEvent.Type.BURNOUT)
@ -543,7 +543,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
// TODO : FUTURE : do not hard code the 1200 (maybe even make it configurable by the user)
// TODO FUTURE : do not hard code the 1200 (maybe even make it configurable by the user)
if( 1200 < currentStatus.getSimulationTime() ){
ret = false;
log.error("Simulation hit max time (1200s): aborting.");
@ -553,6 +553,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// If no motor has ignited, abort
if (!currentStatus.isMotorIgnited()) {
// TODO MEDIUM: display this as a warning to the user (e.g. highlight the cell in the simulation panel in red and a hover: 'make sure the motor ignition is correct' or something)
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noIgnition"));
}

View File

@ -172,7 +172,7 @@ public class FlightEvent implements Comparable<FlightEvent> {
* @return
*/
public void validate(){
if( this.time == Double.NaN ){
if(Double.isNaN(this.time)){
throw new IllegalStateException(type.name()+" event has a NaN time!");
}
switch( this.type ){

View File

@ -4,6 +4,7 @@ import net.sf.openrocket.motor.IgnitionEvent;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.motor.MotorConfigurationId;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RocketComponent;
@ -121,8 +122,8 @@ public class MotorClusterState {
/**
* Compute the average thrust over an interval.
*
* @param simulationTime
* @param cond
* @param startSimulationTime start time of the averaging interval
* @param endSimulationTime end time of the averaging interval
* @return
*/
public double getAverageThrust( final double startSimulationTime, final double endSimulationTime) {
@ -141,7 +142,6 @@ public class MotorClusterState {
* Compute the average thrust over an interval.
*
* @param simulationTime
* @param cond
* @return
*/
public double getThrust( final double simulationTime){
@ -182,9 +182,9 @@ public class MotorClusterState {
currentState = ThrustState.ARMED;
}
public boolean testForIgnition( final FlightEvent _event ){
public boolean testForIgnition(FlightConfiguration flightConfiguration, final FlightEvent _event ){
RocketComponent mount = (RocketComponent) this.getMount();
return getIgnitionEvent().isActivationEvent( _event, mount);
return getIgnitionEvent().isActivationEvent(flightConfiguration, _event, mount);
}
public String toDescription(){

View File

@ -267,22 +267,25 @@ public class BarrowmanCalculatorTest {
public void testContinuousRocket() {
Rocket rocket = TestRockets.makeEstesAlphaIII();
AerodynamicCalculator calc = new BarrowmanCalculator();
FlightConfiguration configuration = rocket.getSelectedConfiguration();
assertTrue("Estes Alpha III should be continous: ", calc.isContinuous( rocket));
assertTrue("Estes Alpha III should be continous: ", calc.isContinuous(configuration, rocket));
}
@Test
public void testContinuousRocketWithStrapOns() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
AerodynamicCalculator calc = new BarrowmanCalculator();
FlightConfiguration configuration = rocket.getSelectedConfiguration();
assertTrue("F9H should be continuous: ", calc.isContinuous( rocket));
assertTrue("F9H should be continuous: ", calc.isContinuous(configuration, rocket));
}
@Test
public void testRadialDiscontinuousRocket() {
Rocket rocket = TestRockets.makeEstesAlphaIII();
AerodynamicCalculator calc = new BarrowmanCalculator();
FlightConfiguration configuration = rocket.getSelectedConfiguration();
NoseCone nose = (NoseCone)rocket.getChild(0).getChild(0);
BodyTube body = (BodyTube)rocket.getChild(0).getChild(1);
@ -291,13 +294,14 @@ public class BarrowmanCalculatorTest {
body.setOuterRadius( 0.012 );
body.setName( body.getName()+" << discontinuous");
assertFalse(" Estes Alpha III has an undetected discontinuity:", calc.isContinuous( rocket));
assertFalse(" Estes Alpha III has an undetected discontinuity:", calc.isContinuous(configuration, rocket));
}
@Test
public void testRadialDiscontinuityWithStrapOns() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
AerodynamicCalculator calc = new BarrowmanCalculator();
FlightConfiguration configuration = rocket.getSelectedConfiguration();
final AxialStage coreStage = (AxialStage)rocket.getChild(1);
final ParallelStage booster = (ParallelStage)coreStage.getChild(0).getChild(0);
@ -309,7 +313,7 @@ public class BarrowmanCalculatorTest {
body.setOuterRadius( 0.012 );
body.setName( body.getName()+" << discontinuous");
assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous( rocket));
assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous(configuration, rocket));
}
@Test

View File

@ -294,9 +294,22 @@ public class FlightConfigurationTest extends BaseTestCase {
config.toggleStage(0);
assertThat(" toggle stage #0: ", config.isStageActive(0), equalTo(false));
AxialStage sustainer = rkt.getTopmostStage();
AxialStage booster = rkt.getBottomCoreStage();
AxialStage sustainer = rkt.getTopmostStage(config);
AxialStage booster = rkt.getBottomCoreStage(config);
assertThat(" sustainer stage is stage #1: ", sustainer.getStageNumber(), equalTo(1));
assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(1));
config.setAllStages();
config._setStageActive(1, false);
sustainer = rkt.getTopmostStage(config);
booster = rkt.getBottomCoreStage(config);
assertThat(" sustainer stage is stage #1: ", sustainer.getStageNumber(), equalTo(0));
assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(0));
config.setAllStages();
sustainer = rkt.getTopmostStage(config);
booster = rkt.getBottomCoreStage(config);
assertThat(" sustainer stage is stage #0: ", sustainer.getStageNumber(), equalTo(0));
assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(1));
@ -351,7 +364,6 @@ public class FlightConfigurationTest extends BaseTestCase {
selected.clearAllStages();
selected.toggleStage(1);
selected.toggleStage(2);
// vvvv Test Target vvvv
InstanceMap instances = selected.getActiveInstances();

View File

@ -0,0 +1,305 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.simulation.exception.MotorIgnitionException;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
import net.sf.openrocket.util.TestRockets;
import org.junit.Assert;
import org.junit.Test;
/**
* Test class that tests the effect on the simulation results of activating/deactivating stages.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class DisableStageTest extends BaseTestCase {
/**
* Tests that the simulation results are correct when a single stage is deactivated and re-activated.
*/
@Test
public void testSingleStage() throws SimulationException {
//// Test disabling the stage
Rocket rocket = TestRockets.makeEstesAlphaIII();
Simulation simDisabled = new Simulation(rocket);
simDisabled.setFlightConfigurationId(TestRockets.TEST_FCID_0);
simDisabled.getActiveConfiguration()._setStageActive(0, false);
simDisabled.getOptions().setISAAtmosphere(true);
simDisabled.getOptions().setTimeStep(0.05);
SimulationListener simulationListener = new AbstractSimulationListener();
// Since there are no stages, the simulation should throw an exception.
try {
simDisabled.simulate(simulationListener);
} catch (SimulationException e) {
if (!(e instanceof MotorIgnitionException)) {
Assert.fail("Simulation should have thrown a MotorIgnitionException");
}
}
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeEstesAlphaIII();
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_0);
simOriginal.getOptions().setISAAtmosphere(true);
simOriginal.getOptions().setTimeStep(0.05);
simDisabled.getActiveConfiguration().setAllStages(); // Re-enable all stages.
double delta = 0.05; // 5 % error margin (simulations are not exact)
compareSims(simOriginal, simDisabled, simulationListener, delta);
}
/**
* Tests that the simulation results are correct when the last stage of a multi-stage rocket is deactivated and re-activated.
*/
@Test
public void testMultiStageLastDisabled() {
//// Test disabling the stage
Rocket rocketRemoved = TestRockets.makeBeta(); // Rocket with the last stage removed
Rocket rocketDisabled = TestRockets.makeBeta(); // Rocket with the last stage disabled
int stageNr = rocketRemoved.getChildCount() - 1;
rocketRemoved.removeChild(stageNr);
FlightConfiguration fc = rocketDisabled.getFlightConfiguration(TestRockets.TEST_FCID_1);
fc._setStageActive(stageNr, false);
Simulation simRemoved = new Simulation(rocketRemoved);
simRemoved.setFlightConfigurationId(TestRockets.TEST_FCID_1);
simRemoved.getOptions().setISAAtmosphere(true);
simRemoved.getOptions().setTimeStep(0.05);
Simulation simDisabled = new Simulation(rocketDisabled);
simDisabled.setFlightConfigurationId(TestRockets.TEST_FCID_1);
simDisabled.getOptions().setISAAtmosphere(true);
simDisabled.getOptions().setTimeStep(0.05);
SimulationListener simulationListener = new AbstractSimulationListener();
double delta = 0.05; // 5 % error margin (simulations are not exact)
compareSims(simRemoved, simDisabled, simulationListener, delta);
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeBeta();
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_1);
simOriginal.getOptions().setISAAtmosphere(true);
simOriginal.getOptions().setTimeStep(0.05);
simDisabled.getActiveConfiguration().setAllStages();
compareSims(simOriginal, simDisabled, simulationListener, delta);
}
/**
* Tests that the simulation results are correct when the first stage of a multi-stage rocket is deactivated and re-activated.
*/
// Don't even know if this test was useful, but simulation results vary wildly because the first stage is disabled,
// so I'm just gonna ignore this test.
/*@Test
public void testMultiStageFirstDisabled() {
//// Test disabling the stage
Rocket rocketRemoved = TestRockets.makeBeta(); // Rocket with the last stage removed
Rocket rocketDisabled = TestRockets.makeBeta(); // Rocket with the last stage disabled
// You need to disable the second stage body tube going into automatic radius mode, otherwise the
// removed and disabled rocket will have different results (removed rocket will have a different diameter)
BodyTube bodyTube = (BodyTube) rocketRemoved.getChild(1).getChild(0);
bodyTube.setOuterRadiusAutomatic(false);
int stageNr = 0;
rocketRemoved.removeChild(stageNr);
FlightConfiguration fc = rocketDisabled.getFlightConfiguration(TestRockets.TEST_FCID_1);
fc._setStageActive(stageNr, false);
Simulation simRemoved = new Simulation(rocketRemoved);
simRemoved.setFlightConfigurationId(TestRockets.TEST_FCID_1);
simRemoved.getOptions().setISAAtmosphere(true);
simRemoved.getOptions().setTimeStep(0.05);
Simulation simDisabled = new Simulation(rocketDisabled);
simDisabled.setFlightConfigurationId(TestRockets.TEST_FCID_1);
simDisabled.getOptions().setISAAtmosphere(true);
simDisabled.getOptions().setTimeStep(0.05);
SimulationListener simulationListener = new AbstractSimulationListener();
double delta = 0.1; // 10 % error margin (simulations are very unstable and not exact when the first stage is disabled...)
compareSims(simRemoved, simDisabled, simulationListener, delta);
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeBeta();
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_1);
simOriginal.getOptions().setISAAtmosphere(true);
simOriginal.getOptions().setTimeStep(0.05);
simDisabled.getActiveConfiguration().setAllStages();
compareSims(simOriginal, simDisabled, simulationListener, delta);
}*/
/**
* Tests that the simulation results are correct when a booster stage is deactivated and re-activated.
*/
@Test
public void testBooster1() {
//// Test disabling the stage
Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed
Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled
FlightConfigurationId fcid = new FlightConfigurationId(TestRockets.FALCON_9H_FCID_1);
int stageNr = 2; // Stage 2 is the Parallel Booster Stage
rocketRemoved.getChild(1).getChild(0).removeChild(0); // Remove the Parallel Booster Stage
FlightConfiguration fc = rocketDisabled.getFlightConfiguration(fcid);
fc._setStageActive(stageNr, false);
Simulation simRemoved = new Simulation(rocketRemoved);
simRemoved.setFlightConfigurationId(fcid);
simRemoved.getOptions().setISAAtmosphere(true);
simRemoved.getOptions().setTimeStep(0.05);
Simulation simDisabled = new Simulation(rocketDisabled);
simDisabled.setFlightConfigurationId(fcid);
simDisabled.getOptions().setISAAtmosphere(true);
simDisabled.getOptions().setTimeStep(0.05);
SimulationListener simulationListener = new AbstractSimulationListener();
double delta = 0.05; // 5 % error margin (simulations are not exact)
compareSims(simRemoved, simDisabled, simulationListener, delta);
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeFalcon9Heavy();
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(fcid);
simOriginal.getOptions().setISAAtmosphere(true);
simOriginal.getOptions().setTimeStep(0.05);
simDisabled.getActiveConfiguration().setAllStages();
compareSims(simOriginal, simDisabled, simulationListener, delta);
}
/**
* Tests that the simulation results are correct when the parent stage of a booster stage is deactivated and re-activated.
*/
@Test
public void testBooster2() {
//// Test disabling the stage
Rocket rocketRemoved = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage removed
Rocket rocketDisabled = TestRockets.makeFalcon9Heavy(); // Rocket with the last stage disabled
FlightConfigurationId fid = new FlightConfigurationId(TestRockets.FALCON_9H_FCID_1);
int stageNr = 1; // Stage 1 is the Parallel Booster Stage's parent stage
rocketRemoved.getChild(1).removeChild(0); // Remove the Parallel Booster Stage's parent stage
FlightConfiguration fc = rocketDisabled.getFlightConfiguration(fid);
fc._setStageActive(stageNr, false);
Simulation simRemoved = new Simulation(rocketRemoved);
simRemoved.setFlightConfigurationId(fid);
simRemoved.getOptions().setISAAtmosphere(true);
simRemoved.getOptions().setTimeStep(0.05);
Simulation simDisabled = new Simulation(rocketDisabled);
simDisabled.setFlightConfigurationId(fid);
simDisabled.getOptions().setISAAtmosphere(true);
simDisabled.getOptions().setTimeStep(0.05);
SimulationListener simulationListener = new AbstractSimulationListener();
// There should be no motors left at this point, so a no motors exception should be thrown
try {
simRemoved.simulate(simulationListener);
} catch (SimulationException e) {
if (!(e instanceof MotorIgnitionException)) {
Assert.fail("Simulation failed: " + e);
}
}
try {
simDisabled.simulate(simulationListener);
} catch (SimulationException e) {
if (!(e instanceof MotorIgnitionException)) {
Assert.fail("Simulation failed: " + e);
}
}
//// Test re-enableing the stage.
Rocket rocketOriginal = TestRockets.makeFalcon9Heavy();
Simulation simOriginal = new Simulation(rocketOriginal);
simOriginal.setFlightConfigurationId(fid);
simOriginal.getOptions().setISAAtmosphere(true);
simOriginal.getOptions().setTimeStep(0.05);
simDisabled.getActiveConfiguration().setAllStages();
double delta = 0.05; // 5 % error margin (simulations are not exact)
compareSims(simOriginal, simDisabled, simulationListener, delta);
}
/**
* Compare simActual to simExpected and fail the unit test if there was an error during simulation or
* the two don't match.
* Tested parameters:
* - maxAcceleration
* - maxAltitude
* - maxVelocity
* - maxMachNumber
* - flightTime
* - launchRodVelocity
* - deploymentVelocity
* - groundHitVelocity
* @param simExpected the expected simulation results
* @param simActual the actual simulation results
* @param simulationListener the simulation listener to use for the comparison
* @param delta the error margin for the comparison (e.g. 0.05 = 5 % error margin)
*/
private void compareSims(Simulation simExpected, Simulation simActual,
SimulationListener simulationListener, double delta) {
try {
simExpected.simulate(simulationListener);
double maxAccelerationOriginal = simExpected.getSimulatedData().getMaxAcceleration();
double maxAltitudeOriginal = simExpected.getSimulatedData().getMaxAltitude();
double maxVelocityOriginal = simExpected.getSimulatedData().getMaxVelocity();
double maxMachNumberOriginal = simExpected.getSimulatedData().getMaxMachNumber();
double flightTimeOriginal = simExpected.getSimulatedData().getFlightTime();
double timeToApogeeOriginal = simExpected.getSimulatedData().getTimeToApogee();
double launchRodVelocityOriginal = simExpected.getSimulatedData().getLaunchRodVelocity();
double deploymentVelocityOriginal = simExpected.getSimulatedData().getDeploymentVelocity();
double groundHitVelocityOriginal = simExpected.getSimulatedData().getGroundHitVelocity();
simActual.simulate(simulationListener);
double maxAccelerationDisabled = simActual.getSimulatedData().getMaxAcceleration();
double maxAltitudeDisabled = simActual.getSimulatedData().getMaxAltitude();
double maxVelocityDisabled = simActual.getSimulatedData().getMaxVelocity();
double maxMachNumberDisabled = simActual.getSimulatedData().getMaxMachNumber();
double flightTimeDisabled = simActual.getSimulatedData().getFlightTime();
double timeToApogeeDisabled = simActual.getSimulatedData().getTimeToApogee();
double launchRodVelocityDisabled = simActual.getSimulatedData().getLaunchRodVelocity();
double deploymentVelocityDisabled = simActual.getSimulatedData().getDeploymentVelocity();
double groundHitVelocityDisabled = simActual.getSimulatedData().getGroundHitVelocity();
Assert.assertEquals(maxAccelerationOriginal, maxAccelerationDisabled, maxAccelerationOriginal * delta);
Assert.assertEquals(maxAltitudeOriginal, maxAltitudeDisabled, maxAltitudeOriginal * delta);
Assert.assertEquals(maxVelocityOriginal, maxVelocityDisabled, maxVelocityOriginal * delta);
Assert.assertEquals(maxMachNumberOriginal, maxMachNumberDisabled, maxMachNumberOriginal * delta);
Assert.assertEquals(flightTimeOriginal, flightTimeDisabled, flightTimeOriginal * delta);
Assert.assertEquals(timeToApogeeOriginal, timeToApogeeDisabled, timeToApogeeOriginal * delta);
Assert.assertEquals(launchRodVelocityOriginal, launchRodVelocityDisabled, launchRodVelocityOriginal * delta);
Assert.assertEquals(deploymentVelocityOriginal, deploymentVelocityDisabled, deploymentVelocityOriginal * delta);
Assert.assertEquals(groundHitVelocityOriginal, groundHitVelocityDisabled, groundHitVelocityOriginal * delta);
} catch (SimulationException e) {
Assert.fail("Simulation failed: " + e);
}
}
}