From 97c1112c56fca381c35d92657af6dbb5e98b8fc6 Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Thu, 5 Mar 2020 23:58:39 -0700 Subject: [PATCH 1/3] Fix ground hit velocity calculation The ground hit velocity is calculated by taking the last recorded total velocity for the flight data and using that as the ground hit velocity if the last altitude measurement is less than 10 m. This method worked until commit f11a3e4 introduced the ability to track and report all events that happened after a GROUND_HIT event. That commit introduced a GroundStepper that will step between the GROUND_HIT event and the SIMULATION_END event and will always add a measurement to the flight data to record the rocket being on the ground. A better way of measuring the ground hit velocity is to interpolate the velocity at the time the GROUND_HIT event, as that would actually be the appropriate ground hit velocity. Closes #576 Signed-off-by: Billy Olsen --- core/src/net/sf/openrocket/simulation/FlightData.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index d0d3d79e4..b31b2f360 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -195,13 +195,7 @@ public class FlightData { maxMachNumber = branch.getMaximum(FlightDataType.TYPE_MACH_NUMBER); flightTime = branch.getLast(FlightDataType.TYPE_TIME); - if (branch.getLast(FlightDataType.TYPE_ALTITUDE) < 10) { - groundHitVelocity = branch.getLast(FlightDataType.TYPE_VELOCITY_TOTAL); - } else { - groundHitVelocity = Double.NaN; - } - // Time to apogee List time = branch.get(FlightDataType.TYPE_TIME); List altitude = branch.get(FlightDataType.TYPE_ALTITUDE); @@ -236,6 +230,10 @@ public class FlightData { double t = event.getTime(); List velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); deploymentVelocity = MathUtil.interpolate( time, velocity, t); + } else if (event.getType() == FlightEvent.Type.GROUND_HIT) { + double t = event.getTime(); + List velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); + groundHitVelocity = MathUtil.interpolate( time, velocity, t); } } From a82d4085e277d17cf9a1ff45948bcf51dd06b09c Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Fri, 6 Mar 2020 07:37:46 -0700 Subject: [PATCH 2/3] Fix typo in method name Fix the spelling of 'interesting' in the method FlightData.calculateInterestingValues. Signed-off-by: Billy Olsen --- core/src/net/sf/openrocket/simulation/FlightData.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index b31b2f360..e7f24f806 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -101,7 +101,7 @@ public class FlightData { for (FlightDataBranch b : branches) this.addBranch(b); - calculateIntrestingValues(); + calculateInterestingValues(); } @@ -126,7 +126,7 @@ public class FlightData { branches.add(branch); if (branches.size() == 1) { - calculateIntrestingValues(); + calculateInterestingValues(); } } @@ -185,7 +185,7 @@ public class FlightData { * Calculate the max. altitude/velocity/acceleration, time to apogee, flight time * and ground hit velocity. */ - private void calculateIntrestingValues() { + private void calculateInterestingValues() { if (branches.isEmpty()) return; From ddfe7c5c903a61008ac7535e75977c673c14804f Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Tue, 10 Mar 2020 11:16:06 -0700 Subject: [PATCH 3/3] Add unit tests for FlightData Add unit tests to cover some of the FlightData object and some of the calculations. This specifically covers the bits for FlightData.getGroundHitVelocity() where the ground hit velocity is calculated from a FlightDataBranch consisting of a series of data points and events occuring during flight. Signed-off-by: Billy Olsen --- .../openrocket/simulation/TestFlightData.java | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 core/test/net/sf/openrocket/simulation/TestFlightData.java diff --git a/core/test/net/sf/openrocket/simulation/TestFlightData.java b/core/test/net/sf/openrocket/simulation/TestFlightData.java new file mode 100644 index 000000000..d9620d529 --- /dev/null +++ b/core/test/net/sf/openrocket/simulation/TestFlightData.java @@ -0,0 +1,285 @@ +package net.sf.openrocket.simulation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import net.sf.openrocket.aerodynamics.WarningSet; + +/** + * Tests the FlightData object. + * + * @author Billy Olsen + */ +public class TestFlightData { + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#FlightData()}. + */ + @Test + public void testFlightData() { + FlightData data = new FlightData(); + + WarningSet warnings = data.getWarningSet(); + assertNotNull(warnings); + assertTrue(warnings.isEmpty()); + + assertEquals(0, data.getBranchCount()); + assertEquals(Double.NaN, data.getDeploymentVelocity(), 0.00); + assertEquals(Double.NaN, data.getFlightTime(), 0.00); + assertEquals(Double.NaN, data.getGroundHitVelocity(), 0.00); + assertEquals(Double.NaN, data.getLaunchRodVelocity(), 0.00); + assertEquals(Double.NaN, data.getMaxAcceleration(), 0.00); + assertEquals(Double.NaN, data.getMaxAltitude(), 0.00); + assertEquals(Double.NaN, data.getMaxMachNumber(), 0.00); + assertEquals(Double.NaN, data.getTimeToApogee(), 0.00); + + } + + /** + * Tests flight data created from summary data. + */ + @Test + public void testFlightDataFromSummaryData() { + double deploymentVelocity = 14.8; + double flightTime = 69.1; + double groundHitVelocity = 3.4; + double launchRodVelocity = 17.5; + double maxAcceleration = 156.2; + double maxVelocity = 105.9; + double maxAltitude = 355.1; + double maxMachNumber = 0.31; + double timeToApogee = 7.96; + + FlightData data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, + maxMachNumber, timeToApogee, flightTime, + groundHitVelocity, launchRodVelocity, + deploymentVelocity); + + WarningSet warnings = data.getWarningSet(); + assertNotNull(warnings); + assertTrue(warnings.isEmpty()); + + assertEquals(0, data.getBranchCount()); + assertEquals(deploymentVelocity, data.getDeploymentVelocity(), 0.00); + assertEquals(flightTime, data.getFlightTime(), 0.00); + assertEquals(groundHitVelocity, data.getGroundHitVelocity(), 0.00); + assertEquals(launchRodVelocity, data.getLaunchRodVelocity(), 0.00); + assertEquals(maxAcceleration, data.getMaxAcceleration(), 0.00); + assertEquals(maxAltitude, data.getMaxAltitude(), 0.00); + assertEquals(maxMachNumber, data.getMaxMachNumber(), 0.00); + assertEquals(timeToApogee, data.getTimeToApogee(), 0.00); + } + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#FlightData(net.sf.openrocket.simulation.FlightDataBranch[])}. + */ + @Test + public void testFlightDataFlightDataBranchArray() { + FlightData data = new FlightData(new FlightDataBranch("Test", FlightDataType.TYPE_TIME)); + + WarningSet warnings = data.getWarningSet(); + assertNotNull(warnings); + assertTrue(warnings.isEmpty()); + + assertEquals(1, data.getBranchCount()); + + data = new FlightData(new FlightDataBranch("Test 1", FlightDataType.TYPE_TIME), + new FlightDataBranch("Test 2", FlightDataType.TYPE_TIME)); + + warnings = data.getWarningSet(); + assertNotNull(warnings); + assertTrue(warnings.isEmpty()); + + assertEquals(2, data.getBranchCount()); + } + + private FlightDataBranch createFlightDataBranch(final String name, final FlightDataType dataType, final double[] values) { + final FlightDataBranch branch = new FlightDataBranch(name, dataType); + addDataPoints(branch, dataType, values); + return branch; + } + + private void addDataPoints(final FlightDataBranch branch, final FlightDataType dataType, final double[] values) { + for (int i=0; i < values.length; i++) { + branch.addPoint(); + branch.setValue(dataType, values[i]); + } + } + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#getMaxAltitude()}. + */ + @Test + public void testGetMaxAltitudeCalculated() { + final double[] altitudes = new double[] { + 10.5, + 37.771, + 37.5, + 5.1, + 0.0 + }; + FlightDataBranch branch = + createFlightDataBranch("Test Max Alt", FlightDataType.TYPE_ALTITUDE, altitudes); + + FlightData data = new FlightData(branch); + + assertEquals(37.771, data.getMaxAltitude(), 0.000); + } + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#getMaxVelocity()}. + */ + @Test + public void testGetMaxVelocityCalculated() { + final double[] velocities = new double[] { + 10.5, + 23.7, + 35.5, + 30.1, + 0.0 + }; + FlightDataBranch branch = + createFlightDataBranch("Test Max Velocity", FlightDataType.TYPE_VELOCITY_TOTAL, velocities); + + FlightData data = new FlightData(branch); + + assertEquals(35.5, data.getMaxVelocity(), 0.000); + } + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#getMaxMachNumber()}. + */ + @Test + public void testGetMaxMachNumberCalculated() { + final double[] machs = new double[] { + 0.1, + 0.2, + 0.333, + 0.3, + 0.1 + }; + FlightDataBranch branch = + createFlightDataBranch("Test Max Mach", FlightDataType.TYPE_MACH_NUMBER, machs); + + FlightData data = new FlightData(branch); + + assertEquals(0.333, data.getMaxMachNumber(), 0.000); + } + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#getFlightTime()}. + */ + @Test + public void testGetFlightTime() { + final double[] times = new double[] { + 1.0, + 5.0, + 15.0, + 20.1, + 30.2 + }; + FlightDataBranch branch = + createFlightDataBranch("Test Flight Time", FlightDataType.TYPE_TIME, times); + + // Flight time is calculated as the last time entry + FlightData data = new FlightData(branch); + assertEquals(30.2, data.getFlightTime(), 0.000); + } + + /** + * Test method for {@link net.sf.openrocket.simulation.FlightData#getGroundHitVelocity()}. + */ + @Test + public void testGetGroundHitVelocity() { + /* + * Setup a flight profile where there is data logged for every second. + * The time events will start at 1, rather than 0 so the time into the + * data log is the index + 1.0 seconds. + * + * i.e. at second 1, the velocity becomes 1.2 m/s, the altitude of the + * rocket is 1.2, and the LiftOff event is saved. + * + * This loosely approximates a flight of about 21 seconds where it + * flies up quickly and comes down quickly. The actual flight profile + * is not as important as the fact that a profile exists. + * + * Each array is stored as + */ + final double[] velocities = new double[] { + // launch to burn out + 1.2, 15.0, 31.1, 45.6, + // burn out to apogee + 33.6, 21.2, 14.1, 6.8, 0.0, + // apogee to ejection charge + 2.4, 4.7, 8.6, 9.23, + // ejection charge to parachute deployment + 7.1, 6.2, 6.2, 6.2, 6.2, + // parachute deployment to ground hit + 6.2, 0.0, 0.0 + }; + final double[] altitudes = new double[] { + // launch to burn out + 1.2, 16.2, 47.3, 92.9, + // burn out to apogee + 126.5, 147.7, 161.8, 168.6, 168.6, + // apogee to ejection charge + 166.2, 161.5, 152.9, 143.67, + // ejection charge to parachute deployment + 136.57, 113.81, 91.05, 68.29, 45.53, + // parachute deployment to ground hit + 22.77, 0.0, 0.0 + }; + final FlightEvent.Type[] eventTypes = new FlightEvent.Type[] { + // launch to burn out + FlightEvent.Type.LIFTOFF, FlightEvent.Type.LAUNCHROD, + FlightEvent.Type.ALTITUDE, FlightEvent.Type.BURNOUT, + + // burn out to apogee + FlightEvent.Type.ALTITUDE, FlightEvent.Type.ALTITUDE, + FlightEvent.Type.ALTITUDE, FlightEvent.Type.ALTITUDE, + FlightEvent.Type.APOGEE, + + // apogee to ejection charge + FlightEvent.Type.ALTITUDE, FlightEvent.Type.ALTITUDE, + FlightEvent.Type.ALTITUDE, FlightEvent.Type.EJECTION_CHARGE, + + // ejection charge to parachute deployment + FlightEvent.Type.ALTITUDE, FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, + FlightEvent.Type.ALTITUDE, FlightEvent.Type.ALTITUDE, + FlightEvent.Type.ALTITUDE, + + // parachute deployment to ground hit + FlightEvent.Type.GROUND_HIT, FlightEvent.Type.ALTITUDE, + FlightEvent.Type.SIMULATION_END + }; + + assertEquals(velocities.length, eventTypes.length); + assertEquals(velocities.length, altitudes.length); + + // This flight data branch only needs to record for time, + // altitude and velocity. + FlightDataBranch branch = + new FlightDataBranch("Ground Hit Velocities", + FlightDataType.TYPE_TIME, + FlightDataType.TYPE_ALTITUDE, + FlightDataType.TYPE_VELOCITY_TOTAL); + + for (int i = 0; i < velocities.length; i++) { + branch.addPoint(); + // the data entries are 1 second ahead of the index + double time = i + 1.0; + branch.setValue(FlightDataType.TYPE_TIME, time); + branch.setValue(FlightDataType.TYPE_ALTITUDE, altitudes[i]); + branch.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, velocities[i]); + branch.addEvent(new FlightEvent(eventTypes[i], time)); + } + + FlightData data = new FlightData(branch); + + assertEquals(6.2, data.getGroundHitVelocity(), 0.000); + } + +}