1 package net.sf.openrocket.aerodynamics;
3 import static net.sf.openrocket.util.MathUtil.pow2;
5 import java.util.Arrays;
6 import java.util.HashMap;
7 import java.util.Iterator;
8 import java.util.LinkedHashMap;
11 import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc;
12 import net.sf.openrocket.aerodynamics.barrowman.RocketComponentCalc;
13 import net.sf.openrocket.rocketcomponent.Configuration;
14 import net.sf.openrocket.rocketcomponent.ExternalComponent;
15 import net.sf.openrocket.rocketcomponent.FinSet;
16 import net.sf.openrocket.rocketcomponent.RocketComponent;
17 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
18 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
19 import net.sf.openrocket.util.Coordinate;
20 import net.sf.openrocket.util.MathUtil;
21 import net.sf.openrocket.util.PolyInterpolator;
22 import net.sf.openrocket.util.Reflection;
25 * An aerodynamic calculator that uses the extended Barrowman method to
26 * calculate the CP of a rocket.
28 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
30 public class BarrowmanCalculator extends AerodynamicCalculator {
32 private static final String BARROWMAN_PACKAGE = "net.sf.openrocket.aerodynamics.barrowman";
33 private static final String BARROWMAN_SUFFIX = "Calc";
36 private Map<RocketComponent, RocketComponentCalc> calcMap = null;
38 private double cacheDiameter = -1;
39 private double cacheLength = -1;
43 public BarrowmanCalculator() {
47 public BarrowmanCalculator(Configuration config) {
53 public BarrowmanCalculator newInstance() {
54 return new BarrowmanCalculator();
59 * Calculate the CP according to the extended Barrowman method.
62 public Coordinate getCP(FlightConditions conditions, WarningSet warnings) {
63 AerodynamicForces forces = getNonAxial(conditions, null, warnings);
70 public Map<RocketComponent, AerodynamicForces> getForceAnalysis(FlightConditions conditions,
71 WarningSet warnings) {
74 Map<RocketComponent, AerodynamicForces> map =
75 new LinkedHashMap<RocketComponent, AerodynamicForces>();
77 // Add all components to the map
78 for (RocketComponent c: configuration) {
79 f = new AerodynamicForces();
83 f.cg = Coordinate.NUL;
84 for (Coordinate coord: c.toAbsolute(c.getCG())) {
85 f.cg = f.cg.average(coord);
92 // Calculate non-axial force data
93 AerodynamicForces total = getNonAxial(conditions, map, warnings);
96 // Calculate friction data
97 total.frictionCD = calculateFrictionDrag(conditions, map, warnings);
98 total.pressureCD = calculatePressureDrag(conditions, map, warnings);
99 total.baseCD = calculateBaseDrag(conditions, map, warnings);
102 total.component = rocket;
103 map.put(rocket, total);
106 for (RocketComponent c: map.keySet()) {
108 if (Double.isNaN(f.baseCD) && Double.isNaN(f.pressureCD) &&
109 Double.isNaN(f.frictionCD))
111 if (Double.isNaN(f.baseCD))
113 if (Double.isNaN(f.pressureCD))
115 if (Double.isNaN(f.frictionCD))
117 f.CD = f.baseCD + f.pressureCD + f.frictionCD;
118 f.Caxial = calculateAxialDrag(conditions, f.CD);
127 public AerodynamicForces getAerodynamicForces(double time, FlightConditions conditions,
128 WarningSet warnings) {
130 if (warnings == null)
131 warnings = ignoreWarningSet;
133 // Calculate non-axial force data
134 AerodynamicForces total = getNonAxial(conditions, null, warnings);
136 // Calculate friction data
137 total.frictionCD = calculateFrictionDrag(conditions, null, warnings);
138 total.pressureCD = calculatePressureDrag(conditions, null, warnings);
139 total.baseCD = calculateBaseDrag(conditions, null, warnings);
141 total.CD = total.frictionCD + total.pressureCD + total.baseCD;
143 total.Caxial = calculateAxialDrag(conditions, total.CD);
146 // Calculate CG and moments of inertia
147 total.cg = this.getCG(time);
148 total.longitudalInertia = this.getLongitudalInertia(time);
149 total.rotationalInertia = this.getRotationalInertia(time);
152 // Calculate pitch and yaw damping moments
153 calculateDampingMoments(conditions, total);
154 total.Cm -= total.pitchDampingMoment;
155 total.Cyaw -= total.yawDampingMoment;
158 // System.out.println("Conditions are "+conditions + "
159 // pitch rate="+conditions.getPitchRate());
160 // System.out.println("Total Cm="+total.Cm+" damping effect="+
161 // (12 * Math.signum(conditions.getPitchRate()) *
162 // MathUtil.pow2(conditions.getPitchRate()) /
163 // MathUtil.pow2(conditions.getVelocity())));
165 // double ef = Math.abs(12 *
166 // MathUtil.pow2(conditions.getPitchRate()) /
167 // MathUtil.pow2(conditions.getVelocity()));
169 //// System.out.println("maxEffect="+maxEffect);
170 // total.Cm -= 12 * Math.signum(conditions.getPitchRate()) *
171 // MathUtil.pow2(conditions.getPitchRate()) /
172 // MathUtil.pow2(conditions.getVelocity());
174 // total.Cyaw -= 0.06 * Math.signum(conditions.getYawRate()) *
175 // MathUtil.pow2(conditions.getYawRate()) /
176 // MathUtil.pow2(conditions.getVelocity());
184 public AerodynamicForces getAxialForces(double time,
185 FlightConditions conditions, WarningSet warnings) {
187 if (warnings == null)
188 warnings = ignoreWarningSet;
190 AerodynamicForces total = new AerodynamicForces();
193 // Calculate friction data
194 total.frictionCD = calculateFrictionDrag(conditions, null, warnings);
195 total.pressureCD = calculatePressureDrag(conditions, null, warnings);
196 total.baseCD = calculateBaseDrag(conditions, null, warnings);
198 total.CD = total.frictionCD + total.pressureCD + total.baseCD;
200 total.Caxial = calculateAxialDrag(conditions, total.CD);
202 // Calculate CG and moments of inertia
203 total.cg = this.getCG(time);
204 total.longitudalInertia = this.getLongitudalInertia(time);
205 total.rotationalInertia = this.getRotationalInertia(time);
215 * Perform the actual CP calculation.
217 private AerodynamicForces getNonAxial(FlightConditions conditions,
218 Map<RocketComponent, AerodynamicForces> map, WarningSet warnings) {
220 AerodynamicForces total = new AerodynamicForces();
223 double radius = 0; // aft radius of previous component
224 double componentX = 0; // aft coordinate of previous component
225 AerodynamicForces forces = new AerodynamicForces();
227 if (warnings == null)
228 warnings = ignoreWarningSet;
230 if (conditions.getAOA() > 17.5*Math.PI/180)
231 warnings.add(new Warning.LargeAOA(conditions.getAOA()));
238 for (RocketComponent component: configuration) {
240 // Skip non-aerodynamic components
241 if (!component.isAerodynamic())
244 // Check for discontinuities
245 if (component instanceof SymmetricComponent) {
246 SymmetricComponent sym = (SymmetricComponent) component;
247 // TODO:LOW: Ignores other cluster components (not clusterable)
248 double x = component.toAbsolute(Coordinate.NUL)[0].x;
250 // Check for lengthwise discontinuity
251 if (x > componentX + 0.0001){
252 if (!MathUtil.equals(radius, 0)) {
253 warnings.add(Warning.DISCONTINUITY);
257 componentX = component.toAbsolute(new Coordinate(component.getLength()))[0].x;
259 // Check for radius discontinuity
260 if (!MathUtil.equals(sym.getForeRadius(), radius)) {
261 warnings.add(Warning.DISCONTINUITY);
262 // TODO: MEDIUM: Apply correction to values to cp and to map
264 radius = sym.getAftRadius();
267 // Call calculation method
269 calcMap.get(component).calculateNonaxialForces(conditions, forces, warnings);
270 forces.cp = component.toAbsolute(forces.cp)[0];
271 forces.Cm = forces.CN * forces.cp.x / conditions.getRefLength();
272 // System.out.println(" CN="+forces.CN+" cp.x="+forces.cp.x+" Cm="+forces.Cm);
275 AerodynamicForces f = map.get(component);
281 f.Cside = forces.Cside;
282 f.Cyaw = forces.Cyaw;
283 f.Croll = forces.Croll;
284 f.CrollDamp = forces.CrollDamp;
285 f.CrollForce = forces.CrollForce;
288 total.cp = total.cp.average(forces.cp);
289 total.CNa += forces.CNa;
290 total.CN += forces.CN;
291 total.Cm += forces.Cm;
292 total.Cside += forces.Cside;
293 total.Cyaw += forces.Cyaw;
294 total.Croll += forces.Croll;
295 total.CrollDamp += forces.CrollDamp;
296 total.CrollForce += forces.CrollForce;
305 //////////////// DRAG CALCULATIONS ////////////////
308 private double calculateFrictionDrag(FlightConditions conditions,
309 Map<RocketComponent, AerodynamicForces> map, WarningSet set) {
310 double c1=1.0, c2=1.0;
312 double mach = conditions.getMach();
319 Re = conditions.getVelocity() * configuration.getLength() /
320 conditions.getAtmosphericConditions().getKinematicViscosity();
322 // System.out.printf("Re=%.3e ", Re);
324 // Calculate the skin friction coefficient (assume non-roughness limited)
325 if (configuration.getRocket().isPerfectFinish()) {
327 // System.out.printf("Perfect finish: Re=%f ",Re);
328 // Assume partial laminar layer. Roughness-limitation is checked later.
332 // System.out.printf("constant Cf=%f ",Cf);
333 } else if (Re < 5.39e5) {
335 Cf = 1.328 / Math.sqrt(Re);
336 // System.out.printf("basic Cf=%f ",Cf);
339 Cf = 1.0/pow2(1.50 * Math.log(Re) - 5.6) - 1700/Re;
340 // System.out.printf("transitional Cf=%f ",Cf);
343 // Compressibility correction
346 // Below Re=1e6 no correction
349 c1 = 1 - 0.1*pow2(mach)*(Re-1e6)/2e6; // transition to turbulent
351 c1 = 1 - 0.1*pow2(mach);
358 c2 = 1 + (1.0 / Math.pow(1+0.045*pow2(mach), 0.25) -1) * (Re-1e6)/2e6;
360 c2 = 1.0 / Math.pow(1+0.045*pow2(mach), 0.25);
365 // System.out.printf("c1=%f c2=%f\n", c1,c2);
366 // Applying continuously around Mach 1
369 } else if (mach < 1.1) {
370 Cf *= (c2 * (mach-0.9)/0.2 + c1 * (1.1-mach)/0.2);
375 // System.out.printf("M=%f Cf=%f (smooth)\n",mach,Cf);
379 // Assume fully turbulent. Roughness-limitation is checked later.
383 // System.out.printf("LOW-TURB ");
386 Cf = 1.0/pow2(1.50 * Math.log(Re) - 5.6);
387 // System.out.printf("NORMAL-TURB ");
390 // Compressibility correction
393 c1 = 1 - 0.1*pow2(mach);
396 c2 = 1/Math.pow(1 + 0.15*pow2(mach), 0.58);
398 // Applying continuously around Mach 1
401 } else if (mach < 1.1) {
402 Cf *= c2 * (mach-0.9)/0.2 + c1 * (1.1-mach)/0.2;
407 // System.out.printf("M=%f, Cd=%f (turbulent)\n", mach,Cf);
411 // Roughness-limited value correction term
412 double roughnessCorrection;
414 roughnessCorrection = 1 - 0.1*pow2(mach);
415 } else if (mach > 1.1) {
416 roughnessCorrection = 1/(1 + 0.18*pow2(mach));
418 c1 = 1 - 0.1*pow2(0.9);
419 c2 = 1.0/(1+0.18 * pow2(1.1));
420 roughnessCorrection = c2 * (mach-0.9)/0.2 + c1 * (1.1-mach)/0.2;
423 // System.out.printf("Cf=%.3f ", Cf);
427 * Calculate the friction drag coefficient.
429 * The body wetted area is summed up and finally corrected with the rocket
430 * fineness ratio (calculated in the same iteration). The fins are corrected
431 * for thickness as we go on.
434 double finFriction = 0;
435 double bodyFriction = 0;
436 double maxR=0, len=0;
438 double[] roughnessLimited = new double[Finish.values().length];
439 Arrays.fill(roughnessLimited, Double.NaN);
441 for (RocketComponent c: configuration) {
443 // Consider only SymmetricComponents and FinSets:
444 if (!(c instanceof SymmetricComponent) &&
445 !(c instanceof FinSet))
448 // Calculate the roughness-limited friction coefficient
449 Finish finish = ((ExternalComponent)c).getFinish();
450 if (Double.isNaN(roughnessLimited[finish.ordinal()])) {
451 roughnessLimited[finish.ordinal()] =
452 0.032 * Math.pow(finish.getRoughnessSize()/configuration.getLength(), 0.2) *
455 // System.out.printf("roughness["+finish+"]=%.3f ",
456 // roughnessLimited[finish.ordinal()]);
460 * Actual Cf is maximum of Cf and the roughness-limited value.
461 * For perfect finish require additionally that Re > 1e6
464 if (configuration.getRocket().isPerfectFinish()) {
466 // For perfect finish require Re > 1e6
467 if ((Re > 1.0e6) && (roughnessLimited[finish.ordinal()] > Cf)) {
468 componentCf = roughnessLimited[finish.ordinal()];
469 // System.out.printf(" rl=%f Cf=%f (perfect=%b)\n",
470 // roughnessLimited[finish.ordinal()],
471 // Cf,rocket.isPerfectFinish());
473 // System.out.printf("LIMITED ");
476 // System.out.printf("NORMAL ");
481 // For fully turbulent use simple max
482 componentCf = Math.max(Cf, roughnessLimited[finish.ordinal()]);
486 // System.out.printf("compCf=%.3f ", componentCf);
491 // Calculate the friction drag:
492 if (c instanceof SymmetricComponent) {
494 SymmetricComponent s = (SymmetricComponent)c;
496 bodyFriction += componentCf * s.getComponentWetArea();
500 map.get(c).frictionCD = componentCf * s.getComponentWetArea()
501 / conditions.getRefArea();
504 double r = Math.max(s.getForeRadius(), s.getAftRadius());
507 len += c.getLength();
509 } else if (c instanceof FinSet) {
511 FinSet f = (FinSet)c;
512 double mac = ((FinSetCalc)calcMap.get(c)).getMACLength();
513 double cd = componentCf * (1 + 2*f.getThickness()/mac) *
514 2*f.getFinCount() * f.getFinArea();
518 map.get(c).frictionCD = cd / conditions.getRefArea();
524 // fB may be POSITIVE_INFINITY, but that's ok for us
525 double fB = (len+0.0001) / maxR;
526 double correction = (1 + 1.0/(2*fB));
528 // Correct body data in map
530 for (RocketComponent c: map.keySet()) {
531 if (c instanceof SymmetricComponent) {
532 map.get(c).frictionCD *= correction;
537 // System.out.printf("\n");
538 return (finFriction + correction*bodyFriction) / conditions.getRefArea();
543 private double calculatePressureDrag(FlightConditions conditions,
544 Map<RocketComponent, AerodynamicForces> map, WarningSet warnings) {
546 double stagnation, base, total;
552 stagnation = calculateStagnationCD(conditions.getMach());
553 base = calculateBaseCD(conditions.getMach());
556 for (RocketComponent c: configuration) {
557 if (!c.isAerodynamic())
560 // Pressure fore drag
561 double cd = calcMap.get(c).calculatePressureDragForce(conditions, stagnation, base,
566 map.get(c).pressureCD = cd;
571 if (c instanceof SymmetricComponent) {
572 SymmetricComponent s = (SymmetricComponent)c;
574 if (radius < s.getForeRadius()) {
575 double area = Math.PI*(pow2(s.getForeRadius()) - pow2(radius));
576 cd = stagnation * area / conditions.getRefArea();
579 map.get(c).pressureCD += cd;
583 radius = s.getAftRadius();
591 private double calculateBaseDrag(FlightConditions conditions,
592 Map<RocketComponent, AerodynamicForces> map, WarningSet warnings) {
596 RocketComponent prevComponent = null;
601 base = calculateBaseCD(conditions.getMach());
604 for (RocketComponent c: configuration) {
605 if (!(c instanceof SymmetricComponent))
608 SymmetricComponent s = (SymmetricComponent)c;
610 if (radius > s.getForeRadius()) {
611 double area = Math.PI*(pow2(radius) - pow2(s.getForeRadius()));
612 double cd = base * area / conditions.getRefArea();
615 map.get(prevComponent).baseCD = cd;
619 radius = s.getAftRadius();
624 double area = Math.PI*pow2(radius);
625 double cd = base * area / conditions.getRefArea();
628 map.get(prevComponent).baseCD = cd;
637 public static double calculateStagnationCD(double m) {
640 pressure = 1 + pow2(m)/4 + pow2(pow2(m))/40;
642 pressure = 1.84 - 0.76/pow2(m) + 0.166/pow2(pow2(m)) + 0.035/pow2(m*m*m);
644 return 0.85 * pressure;
648 public static double calculateBaseCD(double m) {
650 return 0.12 + 0.13 * m*m;
658 private static final double[] axialDragPoly1, axialDragPoly2;
660 PolyInterpolator interpolator;
661 interpolator = new PolyInterpolator(
662 new double[] { 0, 17*Math.PI/180 },
663 new double[] { 0, 17*Math.PI/180 }
665 axialDragPoly1 = interpolator.interpolator(1, 1.3, 0, 0);
667 interpolator = new PolyInterpolator(
668 new double[] { 17*Math.PI/180, Math.PI/2 },
669 new double[] { 17*Math.PI/180, Math.PI/2 },
670 new double[] { Math.PI/2 }
672 axialDragPoly2 = interpolator.interpolator(1.3, 0, 0, 0, 0);
677 * Calculate the axial drag from the total drag coefficient.
683 private double calculateAxialDrag(FlightConditions conditions, double cd) {
684 double aoa = MathUtil.clamp(conditions.getAOA(), 0, Math.PI);
687 // double sinaoa = conditions.getSinAOA();
688 // return cd * (1 + Math.min(sinaoa, 0.25));
693 if (aoa < 17*Math.PI/180)
694 mul = PolyInterpolator.eval(aoa, axialDragPoly1);
696 mul = PolyInterpolator.eval(aoa, axialDragPoly2);
698 if (conditions.getAOA() < Math.PI/2)
705 private void calculateDampingMoments(FlightConditions conditions,
706 AerodynamicForces total) {
708 // Calculate pitch and yaw damping moments
709 if (conditions.getPitchRate() > 0.1 || conditions.getYawRate() > 0.1 || true) {
710 double mul = getDampingMultiplier(conditions, total.cg.x);
711 double pitch = conditions.getPitchRate();
712 double yaw = conditions.getYawRate();
713 double vel = conditions.getVelocity();
715 // double Cm = total.Cm - total.CN * total.cg.x / conditions.getRefLength();
716 // System.out.printf("Damping pitch/yaw, mul=%.4f pitch rate=%.4f "+
717 // "Cm=%.4f / %.4f effect=%.4f aoa=%.4f\n", mul, pitch, total.Cm, Cm,
718 // -(mul * MathUtil.sign(pitch) * pow2(pitch/vel)),
719 // conditions.getAOA()*180/Math.PI);
721 mul *= 3; // TODO: Higher damping yields much more realistic apogee turn
723 // total.Cm -= mul * pitch / pow2(vel);
724 // total.Cyaw -= mul * yaw / pow2(vel);
725 total.pitchDampingMoment = mul * MathUtil.sign(pitch) * pow2(pitch/vel);
726 total.yawDampingMoment = mul * MathUtil.sign(yaw) * pow2(yaw/vel);
728 total.pitchDampingMoment = 0;
729 total.yawDampingMoment = 0;
734 // TODO: MEDIUM: Are the rotation etc. being added correctly? sin/cos theta?
737 private double getDampingMultiplier(FlightConditions conditions, double cgx) {
738 if (cacheDiameter < 0) {
743 for (RocketComponent c: configuration) {
744 if (c instanceof SymmetricComponent) {
745 SymmetricComponent s = (SymmetricComponent)c;
746 area += s.getComponentPlanformArea();
747 cacheLength += s.getLength();
751 cacheDiameter = area / cacheLength;
757 mul = 0.275 * cacheDiameter / (conditions.getRefArea() * conditions.getRefLength());
758 mul *= (MathUtil.pow4(cgx) + MathUtil.pow4(cacheLength - cgx));
761 // TODO: LOW: This could be optimized a lot...
762 for (RocketComponent c: configuration) {
763 if (c instanceof FinSet) {
764 FinSet f = (FinSet)c;
765 mul += 0.6 * Math.min(f.getFinCount(), 4) * f.getFinArea() *
766 MathUtil.pow3(Math.abs(f.toAbsolute(new Coordinate(
767 ((FinSetCalc)calcMap.get(f)).getMidchordPos()))[0].x
769 (conditions.getRefArea() * conditions.getRefLength());
778 //////// The calculator map
781 protected void voidAerodynamicCache() {
782 super.voidAerodynamicCache();
790 private void buildCalcMap() {
791 Iterator<RocketComponent> iterator;
793 calcMap = new HashMap<RocketComponent, RocketComponentCalc>();
795 iterator = rocket.deepIterator();
796 while (iterator.hasNext()) {
797 RocketComponent c = iterator.next();
799 if (!c.isAerodynamic())
802 calcMap.put(c, (RocketComponentCalc) Reflection.construct(BARROWMAN_PACKAGE,
803 c, BARROWMAN_SUFFIX, c));
810 // public static void main(String[] arg) {
812 // PolyInterpolator interpolator;
814 // interpolator = new PolyInterpolator(
815 // new double[] { 0, 17*Math.PI/180 },
816 // new double[] { 0, 17*Math.PI/180 }
818 // double[] poly1 = interpolator.interpolator(1, 1.3, 0, 0);
820 // interpolator = new PolyInterpolator(
821 // new double[] { 17*Math.PI/180, Math.PI/2 },
822 // new double[] { 17*Math.PI/180, Math.PI/2 },
823 // new double[] { Math.PI/2 }
825 // double[] poly2 = interpolator.interpolator(1.3, 0, 0, 0, 0);
828 // for (double a=0; a<=180.1; a++) {
829 // double r = a*Math.PI/180;
830 // if (r > Math.PI/2)
834 // if (r < 18*Math.PI/180)
835 // value = PolyInterpolator.eval(r, poly1);
837 // value = PolyInterpolator.eval(r, poly2);
839 // System.out.println(""+a+" "+value);
845 // Rocket normal = TestRocket.makeRocket();
846 // Rocket perfect = TestRocket.makeRocket();
847 // normal.setPerfectFinish(false);
848 // perfect.setPerfectFinish(true);
850 // Configuration confNormal = new Configuration(normal);
851 // Configuration confPerfect = new Configuration(perfect);
853 // for (RocketComponent c: confNormal) {
854 // if (c instanceof ExternalComponent) {
855 // ((ExternalComponent)c).setFinish(Finish.NORMAL);
858 // for (RocketComponent c: confPerfect) {
859 // if (c instanceof ExternalComponent) {
860 // ((ExternalComponent)c).setFinish(Finish.NORMAL);
865 // confNormal.setToStage(0);
866 // confPerfect.setToStage(0);
870 // BarrowmanCalculator calcNormal = new BarrowmanCalculator(confNormal);
871 // BarrowmanCalculator calcPerfect = new BarrowmanCalculator(confPerfect);
873 // FlightConditions conditions = new FlightConditions(confNormal);
875 // for (double mach=0; mach < 3; mach += 0.1) {
876 // conditions.setMach(mach);
878 // Map<RocketComponent, AerodynamicForces> data =
879 // calcNormal.getForceAnalysis(conditions, null);
880 // AerodynamicForces forcesNormal = data.get(normal);
882 // data = calcPerfect.getForceAnalysis(conditions, null);
883 // AerodynamicForces forcesPerfect = data.get(perfect);
885 // System.out.printf("%f %f %f %f %f %f %f\n",mach,
886 // forcesNormal.pressureCD, forcesPerfect.pressureCD,
887 // forcesNormal.frictionCD, forcesPerfect.frictionCD,
888 // forcesNormal.CD, forcesPerfect.CD);