From b4d058f49c3241d99f090e2892f36a3ed9804666 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 23 Apr 2012 17:51:50 +0000 Subject: [PATCH] Fixed problems in computation of SymmetricComponent.integrate found by new unit tests. There were two problems with the previous code. First due to rounding errors, it was possible during the last iteration, the right end point (x+l) was beyond the overall length of the component. This resulted in a zero 0 being used for r2 throwing off the overall computation of volume. The second problem was for hollow components. The test for when to switch from using filled to hollow was incorrect because it was comparing r to thickness. However, thickness is normal to the surface of the component so this test overestimated the total volume of the component. After making these computation changes, the tests for the density computation in NoseConePresetTests and TransitionPresetTests could be tightened. git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@590 180e2498-e6e9-4542-8430-84ac67f01cd8 --- .../rocketcomponent/SymmetricComponent.java | 44 ++-- .../preset/NoseConePresetTests.java | 10 +- .../preset/TransitionPresetTests.java | 10 +- .../SymmetricComponentVolumeTest.java | 207 ++++++++++++++++++ 4 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java diff --git a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java index 6571aeb9..7473b442 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -298,9 +298,8 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial // Integrate for volume, CG, wetted area and planform area - final double l = length / DIVISIONS; - final double pil = Math.PI * l; // PI * l - final double pil3 = Math.PI * l / 3; // PI * l/3 + final double step = length / DIVISIONS; + final double pi3 = Math.PI / 3.0; r1 = getRadius(0); x = 0; wetArea = 0; @@ -317,25 +316,44 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial * hyp is the length of the hypotenuse from r1 to r2 * height if the y-axis height of the component if not filled */ + /* + * l is the step size for the current loop. Could also be called delta-x. + * + * to account for accumulated errors in the x position during the loop + * during the last iteration (n== DIVISIONS) we recompute l to be + * whatever is left. + */ + double l = (n==DIVISIONS) ? length -x : step; - r2 = getRadius(x + l); + // Further to prevent round off error from the previous statement, + // we clamp r2 to length at the last iteration. + r2 = getRadius((n==DIVISIONS) ? length : x + l); + final double hyp = MathUtil.hypot(r2 - r1, l); - // Volume differential elements final double dV; final double dFullV; - dFullV = pil3 * (r1 * r1 + r1 * r2 + r2 * r2); - if (filled || r1 < thickness || r2 < thickness) { - // Filled piece + dFullV = pi3 * l * (r1 * r1 + r1 * r2 + r2 * r2); + + if ( filled ) { dV = dFullV; } else { - // Hollow piece - final double height = thickness * hyp / l; - dV = MathUtil.max(pil * height * (r1 + r2 - height), 0); + // hollow + // Thickness is normal to the surface of the component + // here we use simple trig to project the Thickness + // on to the y dimension (radius). + double height = thickness * hyp / l; + if (r1 < height || r2 < height) { + // Filled portion of piece + dV = dFullV; + } else { + // Hollow portion of piece + dV = MathUtil.max(Math.PI* l * height * (r1 + r2 - height), 0); + } } - + // Add to the volume-related components volume += dV; fullVolume += dFullV; @@ -348,7 +366,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial final double p = l * (r1 + r2); planArea += p; planCenter += (x + l / 2) * p; - + // Update for next iteration r1 = r2; x += l; diff --git a/core/test/net/sf/openrocket/preset/NoseConePresetTests.java b/core/test/net/sf/openrocket/preset/NoseConePresetTests.java index 14832a28..f36d026e 100644 --- a/core/test/net/sf/openrocket/preset/NoseConePresetTests.java +++ b/core/test/net/sf/openrocket/preset/NoseConePresetTests.java @@ -105,10 +105,8 @@ public class NoseConePresetTests extends BaseTestCase { double density = 100.0 / volume; assertEquals("NoseConeCustom",preset.get(ComponentPreset.MATERIAL).getName()); - // FIXME - I would expect the nc volume computation to be closer for such a simple shape. - // simple math yields 47.74648 - // 100.0/nc.getComponentVolume yields 48.7159 - assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),1.0); + // note - epsilon is 1% of the simple computation of density + assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),0.01*density); } @Test @@ -149,8 +147,8 @@ public class NoseConePresetTests extends BaseTestCase { double density = 100.0 / volume; assertEquals("test",preset.get(ComponentPreset.MATERIAL).getName()); - // FIXME - I would expect the nc volume computation to be closer for such a simple shape. - assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),1.5); + // note - epsilon is 1% of the simple computation of density + assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),0.01*density); } } diff --git a/core/test/net/sf/openrocket/preset/TransitionPresetTests.java b/core/test/net/sf/openrocket/preset/TransitionPresetTests.java index a3f636a7..3b4928fc 100644 --- a/core/test/net/sf/openrocket/preset/TransitionPresetTests.java +++ b/core/test/net/sf/openrocket/preset/TransitionPresetTests.java @@ -110,10 +110,8 @@ public class TransitionPresetTests extends BaseTestCase { double density = 100.0 / volume; assertEquals("TransitionCustom",preset.get(ComponentPreset.MATERIAL).getName()); - // FIXME - I would expect the nc volume computation to be closer for such a simple shape. - // simple math yields 27.2837 - // 100/nc.getComponentVolume yields 27.59832 - assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),0.5); + + assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),0.01*density); } @Test @@ -163,8 +161,8 @@ public class TransitionPresetTests extends BaseTestCase { double density = 100.0 / volume; assertEquals("test",preset.get(ComponentPreset.MATERIAL).getName()); - // FIXME - I would expect the nc volume computation to be closer for such a simple shape. - assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),1.5); + + assertEquals(density,preset.get(ComponentPreset.MATERIAL).getDensity(),0.01*density); } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java new file mode 100644 index 00000000..827782fe --- /dev/null +++ b/core/test/net/sf/openrocket/rocketcomponent/SymmetricComponentVolumeTest.java @@ -0,0 +1,207 @@ +package net.sf.openrocket.rocketcomponent; + +import static org.junit.Assert.assertEquals; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; + +import org.junit.Test; + +public class SymmetricComponentVolumeTest extends BaseTestCase { + + @Test + public void simpleConeFilled() { + NoseCone nc = new NoseCone(); + + final double epsilonPercent = 0.001; + final double density = 2.0; + + nc.setLength(1.0); + nc.setFilled(true); + nc.setType( Transition.Shape.CONICAL ); + nc.setAftRadius(1.0); + nc.setMaterial( new Material.Bulk("test",density,true)); + + System.out.println( nc.getComponentVolume() ); + + double volume = Math.PI / 3.0; + + double mass = density * volume; + + System.out.println( volume ); + + assertEquals( volume, nc.getComponentVolume(), epsilonPercent * volume); + assertEquals( mass, nc.getMass(), epsilonPercent * mass ); + } + + @Test + public void simpleConeHollow() { + NoseCone nc = new NoseCone(); + + final double epsilonPercent = 0.001; + final double density = 2.0; + + nc.setLength(1.0); + nc.setAftRadius(1.0); + nc.setThickness(0.5); + nc.setType( Transition.Shape.CONICAL ); + nc.setMaterial( new Material.Bulk("test",density,true)); + + System.out.println( nc.getComponentVolume() ); + + double volume = Math.PI / 3.0; // outer volume + + // manually projected Thickness of 0.5 on to radius to determine + // the innerConeDimen. Since the outer cone is "square" (height = radius), + // we only need to compute this one dimension in order to compute the + // volume of the inner cone. + double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0; + double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen; + volume -= innerVolume; + + double mass = density * volume; + + System.out.println( volume ); + + assertEquals( volume, nc.getComponentVolume(), epsilonPercent * volume); + assertEquals( mass, nc.getMass(), epsilonPercent * mass ); + } + + @Test + public void simpleConeWithShoulderFilled() { + NoseCone nc = new NoseCone(); + + final double epsilonPercent = 0.001; + final double density = 2.0; + + nc.setLength(1.0); + nc.setFilled(true); + nc.setType( Transition.Shape.CONICAL ); + nc.setAftRadius(1.0); + nc.setAftShoulderRadius(1.0); + nc.setAftShoulderLength(1.0); + nc.setMaterial( new Material.Bulk("test",density,true)); + + + System.out.println( nc.getComponentVolume() ); + + double volume = Math.PI / 3.0; + //volume += Math.PI; + + double mass = density * volume; + + System.out.println( volume ); + + // FIXME - + //assertEquals( volume, nc.getComponentVolume(), epsilonPercent * volume); + assertEquals( mass, nc.getMass(), epsilonPercent * mass ); + } + + @Test + public void simpleTransitionFilled() { + Transition nc = new Transition(); + + final double epsilonPercent = 0.001; + final double density = 2.0; + + nc.setLength(4.0); + nc.setFilled(true); + nc.setType( Transition.Shape.CONICAL ); + nc.setForeRadius(1.0); + nc.setAftRadius(2.0); + nc.setMaterial( new Material.Bulk("test",density,true)); + + System.out.println( nc.getComponentVolume() ); + + double volume = Math.PI / 3.0 * (2.0*2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0; + + double mass = density * volume; + + System.out.println( volume ); + + assertEquals( volume, nc.getComponentVolume(), epsilonPercent * volume); + assertEquals( mass, nc.getMass(), epsilonPercent * mass ); + } + + @Test + public void simpleTransitionHollow1() { + Transition nc = new Transition(); + + final double epsilonPercent = 0.001; + final double density = 2.0; + + nc.setLength(1.0); + nc.setType( Transition.Shape.CONICAL ); + nc.setForeRadius(0.5); + nc.setAftRadius(1.0); + nc.setThickness(0.5); + nc.setMaterial( new Material.Bulk("test",density,true)); + + System.out.println( nc.getComponentVolume() ); + + // Volume of filled transition = + double filledVolume = Math.PI /3.0 * ( 1.0*1.0 + 1.0 * 0.5 + 0.5 * 0.5 ) * 1.0; + + // magic 2D cad drawing... + // + // Since the thickness >= fore radius, the + // hollowed out portion of the transition + // forms a cone. + // the dimensions of this cone were determined + // using a 2d cad tool. + + double innerConeRadius = 0.441; + double innerConeLength = 0.882; + double innerVolume = Math.PI /3.0 * innerConeLength * innerConeRadius * innerConeRadius; + + double volume = filledVolume - innerVolume; + + double mass = density * volume; + + System.out.println( volume ); + + assertEquals( volume, nc.getComponentVolume(), epsilonPercent * volume); + assertEquals( mass, nc.getMass(), epsilonPercent * mass ); + } + + @Test + public void simpleTransitionHollow2() { + Transition nc = new Transition(); + + final double epsilonPercent = 0.001; + final double density = 2.0; + + nc.setLength(1.0); + nc.setType( Transition.Shape.CONICAL ); + nc.setForeRadius(0.5); + nc.setAftRadius(1.0); + nc.setThickness(0.25); + nc.setMaterial( new Material.Bulk("test",density,true)); + + System.out.println( nc.getComponentVolume() ); + + // Volume of filled transition = + double filledVolume = Math.PI /3.0 * ( 1.0*1.0 + 1.0 * 0.5 + 0.5 * 0.5 ) * 1.0; + + // magic 2D cad drawing... + // + // Since the thickness < fore radius, the + // hollowed out portion of the transition + // forms a transition. + // the dimensions of this transition were determined + // using a 2d cad tool. + + double innerTransitionAftRadius = 0.7205; + double innerTransitionForeRadius = 0.2205; + double innerVolume = Math.PI /3.0 * ( innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius); + + double volume = filledVolume - innerVolume; + + double mass = density * volume; + + System.out.println( volume ); + + assertEquals( volume, nc.getComponentVolume(), epsilonPercent * volume); + assertEquals( mass, nc.getMass(), epsilonPercent * mass ); + } + +} -- 2.30.2