From c3cfb2e78165741422e2bc0367fa31873c9cf4f5 Mon Sep 17 00:00:00 2001 From: Kevin Ruland Date: Tue, 17 Jan 2012 02:46:49 +0000 Subject: [PATCH] Persist the Velocity at launch rod clearance in ork file. Added to column in SimulationPanel. Added computation of Velocity at deployment in FlightData. Persist Velocity at deployment in ork file and added to SimulationPanel and DesignReport. Added l10n message keys for new column in SimulationPanel. Added function MathUtil.interpolate which does linear interpolation of intermediate values. This is used in FlightData for computing Velocity at deployment and Velocity at launch rod clearance. --- core/fileformat.txt | 2 + core/resources/l10n/messages.properties | 2 + .../file/openrocket/OpenRocketLoader.java | 12 +- .../file/openrocket/OpenRocketSaver.java | 4 + .../openrocket/gui/main/SimulationPanel.java | 32 +++++ .../sf/openrocket/gui/print/DesignReport.java | 4 + .../sf/openrocket/simulation/FlightData.java | 27 ++-- core/src/net/sf/openrocket/util/MathUtil.java | 131 ++++++++++++------ .../net/sf/openrocket/util/MathUtilTest.java | 60 ++++++++ 9 files changed, 222 insertions(+), 52 deletions(-) diff --git a/core/fileformat.txt b/core/fileformat.txt index 9e04dcfff..17247f26d 100644 --- a/core/fileformat.txt +++ b/core/fileformat.txt @@ -39,3 +39,5 @@ The following file format versions exist: 1.3: Introduced with OpenRocket 1.1.9. Adds the and parameters to the simulation conditions element. +1.4: Introduced with OpenRocket 1.1.10. Adds the launchrodvelocity and + deploymentvelocity attributes to element. \ No newline at end of file diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 3a51de447..e385eb94e 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -378,6 +378,8 @@ simpanel.dlg.lbl.DeleteSim2 = This operation cannot be undone. simpanel.dlg.lbl.DeleteSim3 = Delete simulations simpanel.col.Name = Name simpanel.col.Motors = Motors +simpanel.col.Velocityoffrod = Velocity off rod +simpanel.col.Velocityatdeploy = Velocity at deployment simpanel.col.Apogee = Apogee simpanel.col.Maxvelocity = Max. velocity simpanel.col.Maxacceleration = Max. acceleration diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java index 518f1e44c..475245ddb 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java @@ -1517,6 +1517,8 @@ class FlightDataHandler extends ElementHandler { double timeToApogee = Double.NaN; double flightTime = Double.NaN; double groundHitVelocity = Double.NaN; + double launchRodVelocity = Double.NaN; + double deploymentVelocity = Double.NaN; try { maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude")); @@ -1547,10 +1549,18 @@ class FlightDataHandler extends ElementHandler { DocumentConfig.stringToDouble(attributes.get("groundhitvelocity")); } catch (NumberFormatException ignore) { } + try { + launchRodVelocity = DocumentConfig.stringToDouble(attributes.get("launchrodvelocity")); + } catch (NumberFormatException ignore) { + } + try { + deploymentVelocity = DocumentConfig.stringToDouble(attributes.get("deploymentvelocity")); + } catch (NumberFormatException ignore) { + } // TODO: HIGH: Store and load launchRodVelocity data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach, - timeToApogee, flightTime, groundHitVelocity, Double.NaN); + timeToApogee, flightTime, groundHitVelocity, launchRodVelocity, deploymentVelocity); } data.getWarningSet().addAll(warningSet); diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 7dbb2fce5..c04cce9b2 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -360,6 +360,10 @@ public class OpenRocketSaver extends RocketSaver { str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\""; if (!Double.isNaN(data.getGroundHitVelocity())) str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\""; + if (!Double.isNaN(data.getLaunchRodVelocity())) + str += " launchrodvelocity=\"" + TextUtil.doubleToString(data.getLaunchRodVelocity()) + "\""; + if (!Double.isNaN(data.getDeploymentVelocity())) + str += " deploymentvelocity=\"" + TextUtil.doubleToString(data.getDeploymentVelocity()) + "\""; str += ">"; writeln(str); indent++; diff --git a/core/src/net/sf/openrocket/gui/main/SimulationPanel.java b/core/src/net/sf/openrocket/gui/main/SimulationPanel.java index 11b707eac..719e1cf6a 100644 --- a/core/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/core/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -311,6 +311,22 @@ public class SimulationPanel extends JPanel { } }, + //// Launch rod velocity + new Column(trans.get("simpanel.col.Velocityoffrod")) { + @Override + public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data == null) + return null; + + return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit( + data.getLaunchRodVelocity()); + } + }, + //// Apogee new Column(trans.get("simpanel.col.Apogee")) { @Override @@ -327,6 +343,22 @@ public class SimulationPanel extends JPanel { } }, + //// Velocity at deployment + new Column(trans.get("simpanel.col.Velocityatdeploy")) { + @Override + public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data == null) + return null; + + return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit( + data.getDeploymentVelocity()); + } + }, + //// Maximum velocity new Column(trans.get("simpanel.col.Maxvelocity")) { @Override diff --git a/core/src/net/sf/openrocket/gui/print/DesignReport.java b/core/src/net/sf/openrocket/gui/print/DesignReport.java index 886e225ad..e6290c3e9 100644 --- a/core/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/core/src/net/sf/openrocket/gui/print/DesignReport.java @@ -117,6 +117,7 @@ public class DesignReport { private static final String TIME_TO_APOGEE = "Time to Apogee"; private static final String VELOCITY_OFF_PAD = "Velocity off Pad"; private static final String MAX_VELOCITY = "Max Velocity"; + private static final String DEPLOYMENT_VELOCITY = "Velocity at Deployment"; private static final String LANDING_VELOCITY = "Landing Velocity"; private static final String ROCKET_DESIGN = "Rocket Design"; private static final double GRAVITY_CONSTANT = 9.80665d; @@ -469,6 +470,9 @@ public class DesignReport { labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2)); + labelTable.addCell(ITextHelper.createCell(DEPLOYMENT_VELOCITY, 2,2)); + labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getDeploymentVelocity()),2,2)); + labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2)); diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index 839c65846..c0fb90e80 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -49,6 +49,7 @@ public class FlightData { private double flightTime = Double.NaN; private double groundHitVelocity = Double.NaN; private double launchRodVelocity = Double.NaN; + private double deploymentVelocity = Double.NaN; /** @@ -70,11 +71,12 @@ public class FlightData { * @param timeToApogee time to apogee. * @param flightTime total flight time. * @param groundHitVelocity ground hit velocity. - * @param launchRodVelocity TODO + * @param launchRodVelocity velocity at launch rod clearance + * @param deploymentVelocity velocity at deployment */ public FlightData(double maxAltitude, double maxVelocity, double maxAcceleration, double maxMachNumber, double timeToApogee, double flightTime, - double groundHitVelocity, double launchRodVelocity) { + double groundHitVelocity, double launchRodVelocity, double deploymentVelocity) { this.maxAltitude = maxAltitude; this.maxVelocity = maxVelocity; this.maxAcceleration = maxAcceleration; @@ -83,6 +85,7 @@ public class FlightData { this.flightTime = flightTime; this.groundHitVelocity = groundHitVelocity; this.launchRodVelocity = launchRodVelocity; + this.deploymentVelocity = deploymentVelocity; } @@ -171,7 +174,11 @@ public class FlightData { return launchRodVelocity; } - + + public double getDeploymentVelocity() { + return deploymentVelocity; + } + /** * Calculate the max. altitude/velocity/acceleration, time to apogee, flight time @@ -223,15 +230,11 @@ public class FlightData { if (event.getType() == FlightEvent.Type.LAUNCHROD) { double t = event.getTime(); List velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); - if (velocity == null) { - break; - } - for (int i = 0; i < velocity.size(); i++) { - if (time.get(i) >= t) { - launchRodVelocity = velocity.get(i); - break eventloop; - } - } + launchRodVelocity = MathUtil.interpolate( time, velocity, t); + } else if ( event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { + double t = event.getTime(); + List velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); + deploymentVelocity = MathUtil.interpolate( time, velocity, t); } } diff --git a/core/src/net/sf/openrocket/util/MathUtil.java b/core/src/net/sf/openrocket/util/MathUtil.java index dc2e105d6..c88e85853 100644 --- a/core/src/net/sf/openrocket/util/MathUtil.java +++ b/core/src/net/sf/openrocket/util/MathUtil.java @@ -11,9 +11,9 @@ import net.sf.openrocket.startup.Application; public class MathUtil { private static final LogHelper log = Application.getLogger(); - + public static final double EPSILON = 0.00000001; // 10mm^3 in m^3 - + /** * The square of x (x^2). On Sun's JRE using this method is as fast as typing x*x. * @param x x @@ -22,7 +22,7 @@ public class MathUtil { public static double pow2(double x) { return x * x; } - + /** * The cube of x (x^3). * @param x x @@ -31,11 +31,11 @@ public class MathUtil { public static double pow3(double x) { return x * x * x; } - + public static double pow4(double x) { return (x * x) * (x * x); } - + /** * Clamps the value x to the range min - max. * @param x Original value. @@ -50,7 +50,7 @@ public class MathUtil { return max; return x; } - + public static float clamp(float x, float min, float max) { if (x < min) return min; @@ -58,7 +58,7 @@ public class MathUtil { return max; return x; } - + public static int clamp(int x, int min, int max) { if (x < min) return min; @@ -66,8 +66,8 @@ public class MathUtil { return max; return x; } - - + + /** * Maps a value from one value range to another. * @@ -90,8 +90,8 @@ public class MathUtil { } return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin; } - - + + /** * Maps a coordinate from one value range to another. * @@ -115,8 +115,8 @@ public class MathUtil { double a = (value - fromMin) / (fromMax - fromMin); return toMax.multiply(a).add(toMin.multiply(1 - a)); } - - + + /** * Compute the minimum of two values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -127,7 +127,7 @@ public class MathUtil { return x; return (x < y) ? x : y; } - + /** * Compute the maximum of two values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -138,7 +138,7 @@ public class MathUtil { return y; return (x < y) ? y : x; } - + /** * Compute the minimum of three values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -151,8 +151,8 @@ public class MathUtil { return min(y, z); } } - - + + /** * Compute the minimum of three values. This is performed by direct comparison. @@ -162,8 +162,8 @@ public class MathUtil { public static double min(double w, double x, double y, double z) { return min(min(w, x), min(y, z)); } - - + + /** * Compute the maximum of three values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -176,7 +176,7 @@ public class MathUtil { return max(y, z); } } - + /** * Calculates the hypotenuse sqrt(x^2+y^2). This method is SIGNIFICANTLY * faster than Math.hypot(x,y). @@ -184,7 +184,7 @@ public class MathUtil { public static double hypot(double x, double y) { return Math.sqrt(x * x + y * y); } - + /** * Reduce the angle x to the range 0 - 2*PI. * @param x Original angle. @@ -194,7 +194,7 @@ public class MathUtil { double d = Math.floor(x / (2 * Math.PI)); return x - d * 2 * Math.PI; } - + /** * Reduce the angle x to the range -PI - PI. * @@ -207,8 +207,8 @@ public class MathUtil { double d = Math.rint(x / (2 * Math.PI)); return x - d * 2 * Math.PI; } - - + + /** * Return the square root of a value. If the value is negative, zero is returned. * This is safer in cases where rounding errors might make a value slightly negative. @@ -225,20 +225,20 @@ public class MathUtil { } return Math.sqrt(d); } - - + + public static boolean equals(double a, double b) { double absb = Math.abs(b); - + if (absb < EPSILON / 2) { // Near zero return Math.abs(a) < EPSILON / 2; } return Math.abs(a - b) < EPSILON * absb; } - - + + /** * Return the sign of the number. This corresponds to Math.signum, but ignores * the special cases of zero and NaN. The value returned for those is arbitrary. @@ -251,20 +251,20 @@ public class MathUtil { public static double sign(double x) { return (x < 0) ? -1.0 : 1.0; } - + /* Math.abs() is about 3x as fast as this: - + public static double abs(double x) { return (x<0) ? -x : x; } - */ + */ public static double average(Collection values) { if (values.isEmpty()) { return Double.NaN; } - + double avg = 0.0; int count = 0; for (Number n : values) { @@ -273,12 +273,12 @@ public class MathUtil { } return avg / count; } - + public static double stddev(Collection values) { if (values.size() < 2) { return Double.NaN; } - + double avg = average(values); double stddev = 0.0; int count = 0; @@ -289,12 +289,12 @@ public class MathUtil { stddev = Math.sqrt(stddev / (count - 1)); return stddev; } - + public static double median(Collection values) { if (values.isEmpty()) { return Double.NaN; } - + List sorted = new ArrayList(values); Collections.sort(sorted, new Comparator() { @Override @@ -302,7 +302,7 @@ public class MathUtil { return Double.compare(o1.doubleValue(), o2.doubleValue()); } }); - + int n = sorted.size(); if (n % 2 == 0) { return (sorted.get(n / 2).doubleValue() + sorted.get(n / 2 - 1).doubleValue()) / 2; @@ -310,5 +310,58 @@ public class MathUtil { return sorted.get(n / 2).doubleValue(); } } - + + /** + * Use interpolation to determine the value of the function at point t. + * Current implementation uses simple linear interpolation. The domain + * and range lists must include the same number of values, t must be within + * the domain, and the domain list must be sorted. + * + * @param domain list containing domain samples + * @param range list of corresponding range samples + * @param t domain value at which to interpolate + * @return returns Double.NaN if either list is null or empty or different size, or if t is outsize the domain. + */ + public static double interpolate( List domain, List range, double t ) { + + if ( domain == null || range == null || domain.size() != range.size() ) { + return Double.NaN; + } + + int length = domain.size(); + if ( length <= 1 || t < domain.get(0) || t > domain.get( length-1 ) ) { + return Double.NaN; + } + + // Look for the index of the right end point. + int right = 1; + while( t > domain.get(right) ) { + right ++; + } + int left = right -1; + + // Points are: + + double deltax = domain.get(right) - domain.get(left); + double deltay = range.get(right) - range.get(left); + + // For numerical stability, if deltax is small, + if ( Math.abs(deltax) < EPSILON ) { + if ( deltay < -1.0 * EPSILON ) { + // return neg infinity if deltay is negative + return Double.NEGATIVE_INFINITY; + } + else if ( deltay > EPSILON ) { + // return infinity if deltay is large + return Double.POSITIVE_INFINITY; + } else { + // otherwise return 0 + return 0.0d; + } + } + + return range.get(left) + ( t - domain.get(left) ) * deltay / deltax; + + } + } diff --git a/core/test/net/sf/openrocket/util/MathUtilTest.java b/core/test/net/sf/openrocket/util/MathUtilTest.java index 8b79aeca0..f11ba89c3 100644 --- a/core/test/net/sf/openrocket/util/MathUtilTest.java +++ b/core/test/net/sf/openrocket/util/MathUtilTest.java @@ -216,4 +216,64 @@ public class MathUtilTest { assertEquals(5.43, MathUtil.median(doubles), EPS); } + @Test + public void testInterpolate() { + double v; + List x; + List y; + + x = new ArrayList(); + y = new ArrayList(); + y.add(1.0); + + v= MathUtil.interpolate(null, y, 0.0); + assertEquals("Failed to test for domain null", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x, y, 0.0); + assertEquals("Failed to test for empty domain", Double.NaN, v, EPS); + + x = new ArrayList(); + x.add(1.0); + y = new ArrayList(); + + v = MathUtil.interpolate(x, null, 0.0); + assertEquals("Failed to test for range null", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x, y, 0.0); + assertEquals("Failed to test for empty range", Double.NaN, v, EPS); + + x = new ArrayList(); + x.add(1.0); + x.add(2.0); + y = new ArrayList(); + y.add(15.0); + y.add(17.0); + + v = MathUtil.interpolate(x,y,0.0); + assertEquals("Failed to test t out of domain", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x,y,5.0); + assertEquals("Failed to test t out of domain", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x,y,1.0); + assertEquals("Failed to calculate left endpoint", 15.0, v, EPS); + v = MathUtil.interpolate(x,y,2.0); + assertEquals("Failed to calculate right endpoint", 17.0, v, EPS); + v = MathUtil.interpolate(x,y,1.5); + assertEquals("Failed to calculate center", 16.0, v, EPS); + + x = new ArrayList(); + x.add(0.25); + x.add(0.5); + x.add(1.0); + x.add(2.0); + y = new ArrayList(); + y.add(0.0); + y.add(0.0); + y.add(15.0); + y.add(17.0); + v = MathUtil.interpolate(x,y,1.5); + assertEquals("Failed to calculate center with longer list", 16.0, v, EPS); + + } }