From c482637086621542ea78ba8818f315dce0a11dc2 Mon Sep 17 00:00:00 2001 From: richardgraham Date: Wed, 12 Sep 2012 07:44:13 +0000 Subject: [PATCH] - Implemented a DampingMoment simulation listener example - Added ability for simulation listeners to reserve their own data types. To support this: -- All data types can now be found from just the OpenRocketDocument (reduces some code duplication also). -- Custom expressions rebuilt after loading from file in case they use a listeners reserved type - Fixed (possibly unrelated) issue where datatypes would be deleted and re-made each step if a customexpression used a range or index subexpression git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@1021 180e2498-e6e9-4542-8430-84ac67f01cd8 --- .../document/OpenRocketDocument.java | 44 ++++++ .../openrocket/importt/OpenRocketLoader.java | 10 +- .../openrocket/simulation/FlightDataType.java | 19 ++- .../customexpression/CustomExpression.java | 40 +++++- .../CustomExpressionSimulationListener.java | 12 +- .../customexpression/IndexExpression.java | 6 +- .../customexpression/RangeExpression.java | 5 +- .../listeners/AbstractSimulationListener.java | 11 +- .../SimulationComputationListener.java | 4 + .../listeners/SimulationEventListener.java | 7 + .../listeners/SimulationListener.java | 10 ++ .../listeners/example/DampingMoment.java | 129 ++++++++++++++++++ 12 files changed, 274 insertions(+), 23 deletions(-) create mode 100644 core/src/net/sf/openrocket/simulation/listeners/example/DampingMoment.java diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocument.java b/core/src/net/sf/openrocket/document/OpenRocketDocument.java index 06422181..12535220 100644 --- a/core/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/core/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -1,8 +1,12 @@ package net.sf.openrocket.document; import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import net.sf.openrocket.document.events.DocumentChangeEvent; import net.sf.openrocket.document.events.DocumentChangeListener; @@ -13,7 +17,10 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.customexpression.CustomExpression; +import net.sf.openrocket.simulation.exception.SimulationListenerException; +import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.ArrayList; @@ -121,6 +128,43 @@ public class OpenRocketDocument implements ComponentChangeListener { return customExpressions; } + /* + * Returns a set of all the flight data types defined or available in any way in the rocket document + */ + public Set getFlightDataTypes(){ + Set allTypes = new LinkedHashSet(); + + // built in + Collections.addAll(allTypes, FlightDataType.ALL_TYPES); + + // custom expressions + for (CustomExpression exp : customExpressions){ + allTypes.add(exp.getType()); + } + + // simulation listeners + for (Simulation sim : simulations){ + for (String className : sim.getSimulationListeners()) { + SimulationListener l = null; + try { + Class c = Class.forName(className); + l = (SimulationListener) c.newInstance(); + + Collections.addAll(allTypes, l.getFlightDataTypes()); + //System.out.println("This document has expression datatype from "+l.getName()); + } catch (Exception e) { + log.error("Could not instantiate listener: " + className); + } + } + } + + // imported data + /// not implemented yet + + + return allTypes; + } + public Rocket getRocket() { return rocket; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index f4c615a1..4efdf9db 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -704,8 +704,6 @@ class OpenRocketContentHandler extends AbstractElementHandler { } } - - class DatatypeHandler extends AbstractElementHandler { private final DocumentLoadingContext context; private final OpenRocketContentHandler contentHandler; @@ -1288,10 +1286,14 @@ class SimulationsHandler extends AbstractElementHandler { public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { attributes.remove("status"); + + //Finished loading. Rebuilding custom expressions in case something has changed such as listener variable come available. + for (CustomExpression exp : doc.getCustomExpressions()){ + exp.setExpression(exp.getExpressionString()); + } + super.closeElement(element, attributes, content, warnings); } - - } class SingleSimulationHandler extends AbstractElementHandler { diff --git a/core/src/net/sf/openrocket/simulation/FlightDataType.java b/core/src/net/sf/openrocket/simulation/FlightDataType.java index 251d85d6..adb4b51b 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataType.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataType.java @@ -269,8 +269,8 @@ public class FlightDataType implements Comparable { if (type != null) { // found it from symbol - // if name was not give (empty string), can use the one we found name - if ( s.equals("") || s == null ){ + // if name was not given (empty string), can use the one we found + if ( s == null || s.isEmpty()){ s = type.getName(); } if ( u == null ){ @@ -279,14 +279,19 @@ public class FlightDataType implements Comparable { // if something has changed, then we need to remove the old one // otherwise, just return what we found - if ( !u.equals(type.getUnitGroup()) || - !s.equals(type.getName()) - ) + if ( !u.equals(type.getUnitGroup()) ) { oldPriority = type.priority; - EXISTING_TYPES.remove(type); - log.info("Something changed with the type "+type.getName()+", removed old version."); + log.info("Unitgroup of type "+type.getName() + + ", has changed from "+type.getUnitGroup().toString() + + " to "+u.toString() + + ". Removing old version."); + } + else if (!s.equals(type.getName())) { + oldPriority = type.priority; + EXISTING_TYPES.remove(type); + log.info("Name of type "+type.getName()+", has changed to "+s+". Removing old version."); } else{ return type; diff --git a/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java index 47a5dff1..7102b55b 100644 --- a/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java +++ b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java @@ -34,11 +34,12 @@ public class CustomExpression implements Cloneable{ private List subExpressions = new ArrayList(); public CustomExpression(OpenRocketDocument doc){ + this.doc = doc; + setName(""); setSymbol(""); setUnit(""); setExpression(""); - this.doc = doc; } public CustomExpression(OpenRocketDocument doc, @@ -171,6 +172,7 @@ public class CustomExpression implements Cloneable{ // get a list of all the names of all the available variables protected ArrayList getAllNames(){ ArrayList names = new ArrayList(); + /* for (FlightDataType type : FlightDataType.ALL_TYPES) names.add(type.getName()); @@ -181,12 +183,22 @@ public class CustomExpression implements Cloneable{ names.add(exp.getName()); } } + */ + for (FlightDataType type : doc.getFlightDataTypes()){ + String symb = type.getName(); + if (name == null) continue; + + if (!name.equals( this.getName() )){ + names.add(symb); + } + } return names; } // get a list of all the symbols of the available variables ignoring this one protected ArrayList getAllSymbols(){ ArrayList symbols = new ArrayList(); + /* for (FlightDataType type : FlightDataType.ALL_TYPES) symbols.add(type.getSymbol()); @@ -196,6 +208,14 @@ public class CustomExpression implements Cloneable{ symbols.add(exp.getSymbol()); } } + */ + for (FlightDataType type : doc.getFlightDataTypes()){ + String symb = type.getSymbol(); + if (!symb.equals( this.getSymbol() )){ + symbols.add(symb); + } + } + return symbols; } @@ -305,6 +325,7 @@ public class CustomExpression implements Cloneable{ //// Define the available variables as empty // The built in data types + /* for (FlightDataType type : FlightDataType.ALL_TYPES){ builder.withVariable(new Variable(type.getSymbol())); } @@ -312,12 +333,16 @@ public class CustomExpression implements Cloneable{ for (String symb : getAllSymbols()){ builder.withVariable(new Variable(symb)); } + */ + for (FlightDataType type : doc.getFlightDataTypes()){ + builder.withVariable(new Variable(type.getSymbol())); + } // Try to build try { builder.build(); } catch (Exception e) { - log.user("Custom expression invalid : " + e.toString()); + log.user("Custom expression " + this.toString() + " invalid : " + e.toString()); return false; } @@ -341,14 +366,14 @@ public class CustomExpression implements Cloneable{ * Builds a specified expression, log any errors and returns null in case of error. */ protected Calculable buildExpression(ExpressionBuilder b){ - Calculable calc; + Calculable calc = null; try { calc = b.build(); } catch (UnknownFunctionException e1) { - log.user("Unknown function. Could not build custom expression "+name); + log.user("Unknown function. Could not build custom expression "+this.toString()); return null; } catch (UnparsableExpressionException e1) { - log.user("Unparsable expression. Could not build custom expression "+name+". "+e1.getMessage()); + log.user("Unparsable expression. Could not build custom expression "+this.toString()+". "+e1.getMessage()); return null; } @@ -396,10 +421,13 @@ public class CustomExpression implements Cloneable{ */ public FlightDataType getType(){ + UnitGroup ug = UnitGroup.SIUNITS.get(unit); if ( ug == null ){ + log.debug("SI unit not found for "+unit+" in expression "+toString()+". Making a new fixed unit."); ug = new FixedUnitGroup(unit); } + //UnitGroup ug = new FixedUnitGroup(unit); FlightDataType type = FlightDataType.getType(name, symbol, ug); @@ -442,7 +470,7 @@ public class CustomExpression implements Cloneable{ @Override public String toString(){ - return "Custom expression : "+this.name.toString()+ " " + this.expression.toString(); + return "[Expression name="+this.name.toString()+ " expression=" + this.expression+" unit="+this.unit+"]"; } @Override diff --git a/core/src/net/sf/openrocket/simulation/customexpression/CustomExpressionSimulationListener.java b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpressionSimulationListener.java index d1ce6e97..243f7b15 100644 --- a/core/src/net/sf/openrocket/simulation/customexpression/CustomExpressionSimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpressionSimulationListener.java @@ -3,13 +3,16 @@ package net.sf.openrocket.simulation.customexpression; import java.util.ArrayList; import java.util.List; +import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; +import net.sf.openrocket.startup.Application; public class CustomExpressionSimulationListener extends AbstractSimulationListener { + private static final LogHelper log = Application.getLogger(); private final List expressions; public CustomExpressionSimulationListener(List expressions) { @@ -25,10 +28,17 @@ public class CustomExpressionSimulationListener extends AbstractSimulationListen // Calculate values for custom expressions FlightDataBranch data = status.getFlightData(); for (CustomExpression expression : expressions ) { - data.setValue(expression.getType(), expression.evaluateDouble(status)); + double value = expression.evaluateDouble(status); + //log.debug("Setting value of custom expression "+expression.toString()+" = "+value); + data.setValue(expression.getType(), value); } } + @Override + public boolean isSystemListener(){ + return true; + } + } diff --git a/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java index ab7e1572..6fb084de 100644 --- a/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java +++ b/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java @@ -23,7 +23,6 @@ public class IndexExpression extends CustomExpression { setExpression(indexText); this.setName(""); this.setSymbol(typeText); - } @Override @@ -35,7 +34,10 @@ public class IndexExpression extends CustomExpression { } // From the given datatype, get the time and function values and make an interpolator - FlightDataType type = getType(); + + //Note: must get in a way that flight data system will figure out units. Otherwise there will be a type conflict when we get the new data. + FlightDataType type = FlightDataType.getType(null, getSymbol(), null); + List data = status.getFlightData().get(type); List time = status.getFlightData().get(FlightDataType.TYPE_TIME); LinearInterpolator interp = new LinearInterpolator(time, data); diff --git a/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java index 83b5e6c5..1667b61e 100644 --- a/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java +++ b/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java @@ -40,7 +40,6 @@ public class RangeExpression extends CustomExpression { this.expression = variableType+startTime+endTime; // this is used just for generating the hash log.info("New range expression, "+startTime + " to "+endTime); - } /* @@ -73,7 +72,9 @@ public class RangeExpression extends CustomExpression { } // From the given datatype, get the time and function values and make an interpolator - FlightDataType type = getType(); + + //Note: must get in a way that flight data system will figure out units. Otherwise there will be a type conflict when we get the new data. + FlightDataType type = FlightDataType.getType(null, getSymbol(), null); List data = status.getFlightData().get(type); List time = status.getFlightData().get(FlightDataType.TYPE_TIME); diff --git a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java index a3a3b192..3f156f38 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java @@ -1,5 +1,7 @@ package net.sf.openrocket.simulation.listeners; +import java.util.List; + import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; @@ -8,6 +10,7 @@ import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.simulation.AccelerationData; +import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; @@ -67,7 +70,13 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio return false; } - + /** + * Return an array of any flight data types this listener creates. + */ + @Override + public FlightDataType[] getFlightDataTypes(){ + return new FlightDataType[] {}; + } //// SimulationEventListener //// diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java index be46c50f..3089d053 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java @@ -1,9 +1,12 @@ package net.sf.openrocket.simulation.listeners; +import java.util.List; + import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.simulation.AccelerationData; +import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; @@ -64,4 +67,5 @@ public interface SimulationComputationListener extends SimulationListener { public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException; + public FlightDataType[] getFlightDataTypes(); } diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationEventListener.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationEventListener.java index 6cbb87f3..8ac0e11a 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationEventListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationEventListener.java @@ -1,9 +1,12 @@ package net.sf.openrocket.simulation.listeners; +import java.util.List; + import net.sf.openrocket.motor.MotorId; import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; @@ -56,6 +59,10 @@ public interface SimulationEventListener { */ public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException; + + + + public FlightDataType[] getFlightDataTypes(); } diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java index d5663ccb..322db8e7 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java @@ -1,5 +1,8 @@ package net.sf.openrocket.simulation.listeners; +import java.util.List; + +import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; @@ -76,4 +79,11 @@ public interface SimulationListener { */ public boolean isSystemListener(); + + /** + * Return a list of any flight data types this listener creates. + */ + public FlightDataType[] getFlightDataTypes(); + + } diff --git a/core/src/net/sf/openrocket/simulation/listeners/example/DampingMoment.java b/core/src/net/sf/openrocket/simulation/listeners/example/DampingMoment.java new file mode 100644 index 00000000..b9d2e463 --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/listeners/example/DampingMoment.java @@ -0,0 +1,129 @@ +package net.sf.openrocket.simulation.listeners.example; + +import java.util.List; +import java.util.Map; + +import net.sf.openrocket.aerodynamics.AerodynamicCalculator; +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.motor.MotorId; +import net.sf.openrocket.motor.MotorInstance; +import net.sf.openrocket.motor.MotorInstanceConfiguration; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.simulation.FlightDataBranch; +import net.sf.openrocket.simulation.FlightDataType; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.ArrayList; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.PolyInterpolator; + +public class DampingMoment extends AbstractSimulationListener { + + private static final FlightDataType type = FlightDataType.getType("Damping moment coefficient", "Cdm", UnitGroup.UNITS_COEFFICIENT); + private static final FlightDataType[] typeList = {type}; + + public String getName(){ + return "Damping moment listener"; + } + + /** + * Return a list of any flight data types this listener creates. + */ + public FlightDataType[] getFlightDataTypes(){ + return typeList; + } + + @Override + public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException { + + // Save it as a flightdatatype + + //status.getFlightData().setValue(type, aerodynamicPart + propulsivePart); + status.getFlightData().setValue(type, calculate(status, flightConditions)); + + return flightConditions; + } + + private double calculate(SimulationStatus status, FlightConditions flightConditions){ + + // Work out the propulsive/jet damping part of the moment. + + // dm/dt = (thrust - ma)/v + FlightDataBranch data = status.getFlightData(); + + List mpAll = data.get(FlightDataType.TYPE_PROPELLANT_MASS); + List time = data.get(FlightDataType.TYPE_TIME); + if (mpAll == null || time == null){ + return Double.NaN; + } + + int len = mpAll.size(); + + // This isn't as accurate as I would like + double mdot=Double.NaN; + if (len > 2){ + // Using polynomial interpolator for derivative. Doesn't help much + //double[] x = { time.get(len-5), time.get(len-4), time.get(len-3), time.get(len-2), time.get(len-1) }; + //double[] y = { mpAll.get(len-5), mpAll.get(len-4), mpAll.get(len-3), mpAll.get(len-2), mpAll.get(len-1) }; + //PolyInterpolator interp = new PolyInterpolator(x); + //double[] coeff = interp.interpolator(y); + //double dt = .01; + //mdot = (interp.eval(x[4], coeff) - interp.eval(x[4]-dt, coeff))/dt; + + mdot = (mpAll.get(len-1) - mpAll.get(len-2)) / (time.get(len-1) - time.get(len-2)); + } + + double cg = data.getLast(FlightDataType.TYPE_CG_LOCATION); + + // find the maximum distance from nose to nozzle. + double nozzleDistance = 0; + for (MotorId id: status.getMotorConfiguration().getMotorIDs()){ + MotorInstanceConfiguration config = status.getMotorConfiguration(); + Coordinate position = config.getMotorPosition(id); + + double x = position.x + config.getMotorInstance(id).getParentMotor().getLength(); + if (x > nozzleDistance){ + nozzleDistance = x; + } + } + + // now can get the propulsive part + double propulsivePart = mdot * Math.pow(nozzleDistance - cg, 2); + + // Work out the aerodynamic part of the moment. + double aerodynamicPart = 0; + + AerodynamicCalculator aerocalc = status.getSimulationConditions().getAerodynamicCalculator(); + + // Must go through each component ... + Map forces = aerocalc.getForceAnalysis(status.getConfiguration(), flightConditions, null); + for (Map.Entry entry : forces.entrySet()){ + + RocketComponent comp = entry.getKey(); + + if (!comp.isAerodynamic()) continue; + + //System.out.println(comp.toString()); + + double CNa = entry.getValue().getCNa(); //? + double Cp = entry.getValue().getCP().length(); + double z = comp.getPositionValue(); //? + + aerodynamicPart += CNa*Math.pow(z-Cp, 2); + } + + double v = flightConditions.getVelocity(); + double rho = flightConditions.getAtmosphericConditions().getDensity(); + double ar = flightConditions.getRefArea(); + + aerodynamicPart = aerodynamicPart * .5 * rho * v * ar; + + return aerodynamicPart + propulsivePart; + + } + +} -- 2.30.2