From 027ed2eaa699429c7a010f3a24d4ed53c2aa9c51 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 25 Jun 2022 00:44:54 +0200 Subject: [PATCH] [#1460] Exclude inactive stages in tbe simulation calculations --- .../aerodynamics/AerodynamicCalculator.java | 2 +- .../aerodynamics/BarrowmanCalculator.java | 28 ++++++------ .../openrocket/masscalc/MassCalculator.java | 2 +- .../sf/openrocket/motor/IgnitionEvent.java | 17 ++++---- .../rocketcomponent/AxialStage.java | 7 +-- .../rocketcomponent/FlightConfiguration.java | 18 ++++++-- .../sf/openrocket/rocketcomponent/Rocket.java | 43 ++++++++++++------- .../BasicEventSimulationEngine.java | 15 ++++--- .../sf/openrocket/simulation/FlightEvent.java | 2 +- .../simulation/MotorClusterState.java | 10 ++--- .../aerodynamics/BarrowmanCalculatorTest.java | 12 ++++-- .../FlightConfigurationTest.java | 4 +- 12 files changed, 95 insertions(+), 65 deletions(-) diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java index d51448699..fbdac87e4 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -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); } diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 0b7b3674d..9335b4b64 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -81,7 +81,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { Map 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()); @@ -126,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 eachForces, @@ -154,12 +155,11 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { for( RocketComponent child : comp.getChildren()) { // Ignore inactive stages - if (child instanceof AxialStage && - !((AxialStage) child).isStageActive()) { + 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); @@ -246,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); } @@ -272,16 +272,15 @@ 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 queue = new LinkedList<>(); for (RocketComponent child : treeRoot.getChildren()) { // Ignore inactive stages - if (child instanceof AxialStage && - !((AxialStage) child).isStageActive()) { + if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { continue; } queue.add(child); @@ -294,8 +293,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { if( comp instanceof SymmetricComponent ){ for (RocketComponent child : comp.getChildren()) { // Ignore inactive stages - if (child instanceof AxialStage && - !((AxialStage) child).isStageActive()) { + if (child instanceof AxialStage && !configuration.isStageActive(child.getStageNumber())) { continue; } queue.add(child); @@ -323,7 +321,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { prevComp = sym; }else if( comp instanceof ComponentAssembly ){ - isContinuous &= testIsContinuous( comp ); + isContinuous &= testIsContinuous(configuration, comp); } } @@ -339,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, diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index 463ed7f4b..4b36d03dc 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -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 activeMotorList = status.getMotors(); + final Collection activeMotorList = status.getActiveMotors(); MassCalculation calculation= new MassCalculation( _type, config, time, activeMotorList, config.getRocket(), Transformation.IDENTITY, null); calculation.calculateAssembly(); diff --git a/core/src/net/sf/openrocket/motor/IgnitionEvent.java b/core/src/net/sf/openrocket/motor/IgnitionEvent.java index 60a2bf2ea..619642167 100644 --- a/core/src/net/sf/openrocket/motor/IgnitionEvent.java +++ b/core/src/net/sf/openrocket/motor/IgnitionEvent.java @@ -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; } diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java index 289f98c2d..e4ff3bb7c 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java @@ -130,11 +130,12 @@ 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 ((this instanceof ParallelStage && config.isStageActive(this.stageNumber)) + ||( getRocket().getBottomCoreStage(config).equals(this))); } /** diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 2ae0211fa..09c090f11 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -60,8 +60,8 @@ public class FlightConfiguration implements FlightConfigurableParameter stages = new HashMap(); - final protected HashMap motors = new HashMap(); + final protected Map stages = new HashMap(); + final protected Map motors = new HashMap(); final private Collection activeMotors = new ArrayList(); final private InstanceMap activeInstances = new InstanceMap(); @@ -190,7 +190,7 @@ public class FlightConfiguration implements FlightConfigurableParameterfalse) or active (true) */ - private void _setStageActive(final int stageNumber, final boolean _active ) { + public void _setStageActive(final int stageNumber, final boolean _active ) { if ((0 <= stageNumber) && (stages.containsKey(stageNumber))) { stages.get(stageNumber).active = _active; fireChangeEvent(); @@ -338,6 +338,18 @@ public class FlightConfiguration implements FlightConfigurableParameter getAllStages() { + List stages = new ArrayList<>(); + for (StageFlags flags : this.stages.values()) { + stages.add( rocket.getStage(flags.stageNumber)); + } + return stages; + } public List getActiveStages() { List activeStages = new ArrayList<>(); diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 7a3f1780a..3855db54c 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -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 diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 8fdae800a..1d5b62e76 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -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")); } diff --git a/core/src/net/sf/openrocket/simulation/FlightEvent.java b/core/src/net/sf/openrocket/simulation/FlightEvent.java index 55ceaab36..b78ad1f3b 100644 --- a/core/src/net/sf/openrocket/simulation/FlightEvent.java +++ b/core/src/net/sf/openrocket/simulation/FlightEvent.java @@ -172,7 +172,7 @@ public class FlightEvent implements Comparable { * @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 ){ diff --git a/core/src/net/sf/openrocket/simulation/MotorClusterState.java b/core/src/net/sf/openrocket/simulation/MotorClusterState.java index 43c73f9c3..728287c96 100644 --- a/core/src/net/sf/openrocket/simulation/MotorClusterState.java +++ b/core/src/net/sf/openrocket/simulation/MotorClusterState.java @@ -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(){ diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 9f285a709..828a49447 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -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 diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index c4ad802af..31907369f 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -295,8 +295,8 @@ 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 #0: ", sustainer.getStageNumber(), equalTo(0)); assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(1));