From 9dc26a3e66667521cc60fe45085488a024a4dcf1 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 23 Oct 2022 23:00:42 +0200 Subject: [PATCH 01/17] MAINT: Introducing AeroSurfaces classes --- rocketpy/AeroSurfaces.py | 656 +++++++++++++++++++++++++++++++++++++++ rocketpy/Flight.py | 7 +- rocketpy/Rocket.py | 439 +++----------------------- 3 files changed, 710 insertions(+), 392 deletions(-) create mode 100644 rocketpy/AeroSurfaces.py diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py new file mode 100644 index 000000000..feb9699b4 --- /dev/null +++ b/rocketpy/AeroSurfaces.py @@ -0,0 +1,656 @@ +__author__ = "Guilherme Fernandes Alves" +__copyright__ = "Copyright 20XX, RocketPy Team" +__license__ = "MIT" + +import numpy as np +from .Function import Function + + +class NoseCone: + """Keeps nose cone information. + + Attributes + ---------- + NoseCone.length : float + Nose cone length. Has units of length and must be given in meters. + NoseCone.kind : string + Nose cone kind. Can be "conical", "ogive" or "lvhaack". + NoseCone.distanceToCM : float + Distance between nose cone tip and rocket center of mass. Has units of + length and must be given in meters. + NoseCone.name : string + Nose cone name. Has no impact in simulation, as it is only used to + display data in a more organized matter. + NoseCone.cp : tuple + Tuple with the x, y and z coordinates of the nose cone center of pressure + relative to the rocket center of mass. Has units of length and must be + given in meters. + NoseCone.cl : Function + Function which defines the lift coefficient as a function of the angle of + attack and the Mach number. It must take as input the angle of attack in + radians and the Mach number. It should return the lift coefficient. + """ + + def __init__(self, length, kind, distanceToCM, name="Nose Cone"): + """Initializes the nose cone. It is used to define the nose cone + length, kind, distance to center of mass and name. + + Parameters + ---------- + length : float + Nose cone length. Has units of length and must be given in meters. + kind : string + Nose cone kind. Can be "conical", "ogive" or "lvhaack". + distanceToCM : _type_ + Distance between nose cone tip and rocket center of mass. Has units of + length and must be given in meters. + name : str, optional + Nose cone name. Has no impact in simulation, as it is only used to + display data in a more organized matter. + + Returns + ------- + None + """ + self.length = length + self.kind = kind + self.distanceToCM = distanceToCM + self.name = name + + # Analyze type + if self.kind == "conical": + self.k = 1 - 1 / 3 + elif self.kind == "ogive": + self.k = 1 - 0.534 + elif self.kind == "lvhaack": + self.k = 1 - 0.437 + else: + self.k = 0.5 + # Calculate cp position relative to cm + self.cpz = self.distanceToCM + np.sign(self.distanceToCM) * self.k * length + self.cpy = 0 + self.cpx = 0 + self.cp = (self.cpx, self.cpy, self.cpz) + + # Calculate clalpha + self.clalpha = 2 + self.cl = Function( + lambda alpha, mach: self.clalpha * alpha, + ["Alpha (rad)", "Mach"], + "Cl", + ) + # # Store values + # nose = {"cp": (0, 0, self.cpz), "cl": self.cl, "name": name} + + return None + + +class TrapezoidalFins: + """Keeps trapezoidal fins information. + + Attributes + ---------- + + """ + + def __init__( + self, + n, + rootChord, + tipChord, + span, + distanceToCM, + cantAngle=0, + sweepLength=None, + sweepAngle=None, + radius=None, + airfoil=None, + name="Fins", + ): + """Initializes the trapezoidal fins. It is used to define the number of + fins, root chord, tip chord, span, distance to center of mass, cant angle + and name. + + Parameters + ---------- + n : int + Number of fins, from 2 to infinity. + span : int, float + Fin span in meters. + rootChord : int, float + Fin root chord in meters. + tipChord : int, float + Fin tip chord in meters. + distanceToCM : int, float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. Consider the center point belonging to the top + of the fins to calculate distance. + cantAngle : int, float, optional + Fins cant angle with respect to the rocket centerline. Must + be given in degrees. + radius : int, float, optional + Reference radius to calculate lift coefficient. If None, which + is default, use rocket radius. Otherwise, enter the radius + of the rocket in the section of the fins, as this impacts + its lift coefficient. + airfoil : tuple, optional + Default is null, in which case fins will be treated as flat plates. + Otherwise, if tuple, fins will be considered as airfoils. The + tuple's first item specifies the airfoil's lift coefficient + by angle of attack and must be either a .csv, .txt, ndarray + or callable. The .csv and .txt files must contain no headers + and the first column must specify the angle of attack, while + the second column must specify the lift coefficient. The + ndarray should be as [(x0, y0), (x1, y1), (x2, y2), ...] + where x0 is the angle of attack and y0 is the lift coefficient. + If callable, it should take an angle of attack as input and + return the lift coefficient at that angle of attack. + The tuple's second item is the unit of the angle of attack, + accepting either "radians" or "degrees". + + Returns + ------- + None + """ + # Store values + self.numberOfFins = n + self.finRadius = radius + self.finAirfoil = airfoil + self.finDistanceToCM = distanceToCM + self.finCantAngle = cantAngle + self.finRootChord = rootChord + self.finTipChord = tipChord + self.finSpan = span + self.name = name + + # get some nicknames + Cr, Ct = self.finRootChord, self.finTipChord + s = self.finSpan + cantAngleRad = np.radians(cantAngle) + + # Check if sweep angle or sweep length is given + if sweepLength is not None and sweepAngle is not None: + raise ValueError("Cannot use sweepLength and sweepAngle together") + elif sweepAngle is not None: + sweepLength = np.tan(sweepAngle * np.pi / 180) * span + elif sweepLength is None: + sweepLength = Cr - Ct + else: + # Sweep length is given + pass + + # Compute auxiliary geometrical parameters + d = 2 * radius + Aref = np.pi * radius**2 + Yr = Cr + Ct + Af = Yr * s / 2 # Fin area + AR = 2 * s**2 / Af # Fin aspect ratio + gamma_c = np.arctan( + (sweepLength + 0.5 * Ct - 0.5 * Cr) / (span) + ) # Mid chord angle + Yma = (s / 3) * (Cr + 2 * Ct) / Yr # Span wise coord of mean aero chord + + # Center of pressure position relative to CDM (center of dry mass) + cpz = distanceToCM + np.sign(distanceToCM) * ( + ((Cr - Ct) / 3) * ((Cr + 2 * Ct) / (Cr + Ct)) + + (1 / 6) * (Cr + Ct - Cr * Ct / (Cr + Ct)) + ) + + # Fin–body interference correction parameters + tau = (s + radius) / radius + liftInterferenceFactor = 1 + 1 / tau + λ = Ct / Cr + + # Defines beta parameter + def beta(mach): + """Defines a parameter that is commonly used in aerodynamic + equations. It is commonly used in the Prandtl factor which + corrects subsonic force coefficients for compressible flow. + + Parameters + ---------- + mach : int, float + Number of mach. + + Returns + ------- + beta : int, float + Value that characterizes flow speed based on the mach number. + """ + + if mach < 0.8: + return np.sqrt(1 - mach**2) + elif mach < 1.1: + return np.sqrt(1 - 0.8**2) + else: + return np.sqrt(mach**2 - 1) + + # Defines number of fins factor + def finNumCorrection(n): + """Calculates a correction factor for the lift coefficient of multiple fins. + The specifics values are documented at: + Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development + of an Open Source model rocket simulation software. + + Parameters + ---------- + n : int + Number of fins. + + Returns + ------- + Corrector factor : int + Factor that accounts for the number of fins. + """ + correctorFactor = [2.37, 2.74, 2.99, 3.24] + if n >= 5 and n <= 8: + return correctorFactor[n - 5] + else: + return n / 2 + + if not airfoil: + # Defines clalpha2D as 2*pi for planar fins + clalpha2D = Function(lambda mach: 2 * np.pi / beta(mach)) + else: + # Defines clalpha2D as the derivative of the + # lift coefficient curve for a specific airfoil + airfoilCl = Function( + airfoil[0], + interpolation="linear", + ) + + # Differentiating at x = 0 to get cl_alpha + clalpha2D_Mach0 = airfoilCl.differentiate(x=1e-3, dx=1e-3) + + # Convert to radians if needed + if airfoil[1] == "degrees": + clalpha2D_Mach0 *= 180 / np.pi + + # Correcting for compressible flow + clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) + + # Diederich's Planform Correlation Parameter + FD = 2 * np.pi * AR / (clalpha2D * np.cos(gamac)) + + # Lift coefficient derivative for a single fin + clalphaSingleFin = Function( + lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref) * np.cos(gamac)) + / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) + ) + + # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference + clalphaMultipleFins = ( + liftInterferenceFactor * finNumCorrection(n) * clalphaSingleFin + ) # Function of mach number + + # Calculates clalpha * alpha + cl = Function( + lambda alpha, mach: alpha * clalphaMultipleFins(mach), + ["Alpha (rad)", "Mach"], + "Cl", + ) + + # Parameters for Roll Moment. + # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf + rollDampingInterferenceFactor = 1 + ( + ((tau - λ) / (tau)) - ((1 - λ) / (tau - 1)) * np.log(tau) + ) / ( + ((tau + 1) * (tau - λ)) / (2) - ((1 - λ) * (tau**3 - 1)) / (3 * (tau - 1)) + ) + rollForcingInterferenceFactor = (1 / np.pi**2) * ( + (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) + + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) + + ((tau**2 + 1) ** 2) + / (tau**2 * (tau - 1) ** 2) + * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 + - (4 * (tau + 1)) + / (tau * (tau - 1)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) + ) + clfDelta = ( + rollForcingInterferenceFactor * n * (Yma + radius) * clalphaSingleFin / d + ) # Function of mach number + cldOmega = ( + 2 + * rollDampingInterferenceFactor + * n + * clalphaSingleFin + * np.cos(cantAngleRad) + * rollGeometricalConstant + / (Aref * d**2) + ) # Function of mach number + rollParameters = [clfDelta, cldOmega, cantAngleRad] + + # Save and store parameters + self.rollParameters = rollParameters + self.cl = cl + self.clalphaSingleFin = clalphaSingleFin + self.clalphaMultipleFins = clalphaMultipleFins + self.cpx = 0 + self.cpy = 0 + self.cpz = cpz + self.cp = (self.cpx, self.cpy, self.cpz) + + def info(self): + "Still not implemented. Must print important information." + + return None + + return None + + +class EllipticalFins: + """Class that defines the aerodynamic model of an elliptical fin. + + Parameters + ---------- + n : int + Number of fins. + radius : int, float + Fin radius. + span : int, float + Fin span. + cantAngle : int, float + Cant angle of the fin. + + Returns + ------- + None + """ + + def __init__( + self, + n, + rootChord, + span, + distanceToCM, + cantAngle=0, + radius=None, + airfoil=None, + name="Fins", + ): + """Initializes the class, defining the parameters of the fins. + + Parameters + ---------- + n : int + Number of fins. + rootChord : _type_ + _description_ + span : _type_ + _description_ + distanceToCM : _type_ + _description_ + cantAngle : int, optional + _description_, by default 0 + radius : _type_, optional + _description_, by default None + airfoil : _type_, optional + _description_, by default None + name : str, optional + _description_, by default "Fins" + + Returns + ------- + None + + """ + + # Save attributes + self.numberOfFins = n + self.rootChord = rootChord + self.span = span + self.distanceToCM = distanceToCM + self.cantAngle = cantAngle + self.radius = radius + self.airfoil = airfoil + self.name = name + + # Get some nicknames + Cr = self.rootChord + s = self.span + cantAngleRad = np.radians(cantAngle) + + # Compute auxiliary geometrical parameters + d = 2 * radius + Aref = np.pi * radius**2 # Reference area for coefficients + Af = (np.pi * Cr / 2 * s) / 2 # Fin area + AR = 2 * s**2 / Af # Fin aspect ratio + Yma = ( + s / (3 * np.pi) * np.sqrt(9 * np.pi**2 - 16) + ) # Span wise coord of mean aero chord + rollGeometricalConstant = ( + Cr + * s + * (3 * np.pi * s**2 + 32 * radius * s + 12 * np.pi * radius**2) + / 48 + ) + + # Center of pressure position relative to CDM (center of dry mass) + cpz = distanceToCM + np.sign(distanceToCM) * (0.288 * Cr) + + # Fin–body interference correction parameters + tau = (s + radius) / radius + liftInterferenceFactor = 1 + 1 / tau + rollDampingInterferenceFactor = 1 + ( + (radius**2) + * ( + 2 + * (radius**2) + * np.sqrt(s**2 - radius**2) + * np.log((2 * s * np.sqrt(s**2 - radius**2) + 2 * s**2) / radius) + - 2 * (radius**2) * np.sqrt(s**2 - radius**2) * np.log(2 * s) + + 2 * s**3 + - np.pi * radius * s**2 + - 2 * (radius**2) * s + + np.pi * radius**3 + ) + ) / (2 * (s**2) * (s / 3 + np.pi * radius / 4) * (s**2 - radius**2)) + rollForcingInterferenceFactor = (1 / np.pi**2) * ( + (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) + + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) + + ((tau**2 + 1) ** 2) + / (tau**2 * (tau - 1) ** 2) + * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 + - (4 * (tau + 1)) + / (tau * (tau - 1)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) + ) + + # Auxiliary functions + # Defines beta parameter + def beta(mach): + """Defines a parameter that is commonly used in aerodynamic + equations. It is commonly used in the Prandtl factor which + corrects subsonic force coefficients for compressible flow. + + Parameters + ---------- + mach : int, float + Number of mach. + + Returns + ------- + beta : int, float + Value that characterizes flow speed based on the mach number. + """ + + if mach < 0.8: + return np.sqrt(1 - mach**2) + elif mach < 1.1: + return np.sqrt(1 - 0.8**2) + else: + return np.sqrt(mach**2 - 1) + + # Defines number of fins correction + def finNumCorrection(n): + """Calculates a corrector factor for the lift coefficient of multiple fins. + The specifics values are documented at: + Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development + of an Open Source model rocket simulation software. + + Parameters + ---------- + n : int + Number of fins. + + Returns + ------- + Corrector factor : int + Factor that accounts for the number of fins. + """ + correctorFactor = [2.37, 2.74, 2.99, 3.24] + if n >= 5 and n <= 8: + return correctorFactor[n - 5] + else: + return n / 2 + + if not airfoil: + # Defines clalpha2D as 2*pi for planar fins + clalpha2D = Function(lambda mach: 2 * np.pi / beta(mach)) + else: + # Defines clalpha2D as the derivative of the + # lift coefficient curve for a specific airfoil + airfoilCl = Function( + airfoil[0], + interpolation="linear", + ) + + # Differentiating at x = 0 to get cl_alpha + clalpha2D_Mach0 = airfoilCl.differentiate(x=1e-3, dx=1e-3) + + # Convert to radians if needed + if airfoil[1] == "degrees": + clalpha2D_Mach0 *= 180 / np.pi + + # Correcting for compressible flow + clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) + # Diederich's Planform Correlation Parameter + FD = 2 * np.pi * AR / (clalpha2D) + + # Lift coefficient derivative for a single fin + clalphaSingleFin = Function( + lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref)) + / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) + ) + + # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference + clalphaMultipleFins = ( + liftInterferenceFactor * finNumCorrection(n) * clalphaSingleFin + ) # Function of mach number + + # Calculates clalpha * alpha + cl = Function( + lambda alpha, mach: alpha * clalphaMultipleFins(mach), + ["Alpha (rad)", "Mach"], + "Cl", + ) + + # Parameters for Roll Moment. + # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/develop/docs/technical/aerodynamics/Roll_Equations.pdf + clfDelta = ( + rollForcingInterferenceFactor * n * (Yma + radius) * clalphaSingleFin / d + ) # Function of mach number + cldOmega = ( + 2 + * rollDampingInterferenceFactor + * n + * clalphaSingleFin + * np.cos(cantAngleRad) + * rollGeometricalConstant + / (Aref * d**2) + ) + # Function of mach number + rollParameters = [clfDelta, cldOmega, cantAngleRad] + + # Store values + self.cpx = 0 + self.cpy = 0 + self.cpz = cpz + self.cp = (self.cpx, self.cpy, self.cpz) + self.cl = cl + self.rollParameters = rollParameters + self.clalphaMultipleFins = clalphaMultipleFins + self.clalphaSingleFin = clalphaSingleFin + + return None + + +class Tail: + """Class that defines a tail for the rocket. + + Parameters + ---------- + length : int, float + Length of the tail. + ... + + + """ + + def __init__( + self, topRadius, bottomRadius, length, distanceToCM, radius, name="Tail" + ): + """_summary_ + + Parameters + ---------- + topRadius : _type_ + _description_ + bottomRadius : _type_ + _description_ + length : _type_ + _description_ + distanceToCM : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + + # Store arguments as attributes + self.tailTopRadius = topRadius + self.tailBottomRadius = bottomRadius + self.tailLength = length + self.tailDistanceToCM = distanceToCM + self.name = name + self.radius = radius + + # Calculate ratio between top and bottom radius + r = topRadius / bottomRadius + + # Retrieve reference radius + rref = self.radius + + # Calculate cp position relative to center of dry mass + if distanceToCM < 0: + cpz = distanceToCM - (length / 3) * (1 + (1 - r) / (1 - r**2)) + else: + cpz = distanceToCM + (length / 3) * (1 + (1 - r) / (1 - r**2)) + + # Calculate clalpha + clalpha = -2 * (1 - r ** (-2)) * (topRadius / rref) ** 2 + cl = Function( + lambda alpha, mach: clalpha * alpha, + ["Alpha (rad)", "Mach"], + "Cl", + ) + + # Store values as class attributes + self.cpx = 0 + self.cpy = 0 + self.cpz = cpz + self.cp = (self.cpx, self.cpy, self.cpz) + self.cl = cl + self.clalpha = clalpha + + return None diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py index da2581077..172f584ec 100644 --- a/rocketpy/Flight.py +++ b/rocketpy/Flight.py @@ -1385,7 +1385,7 @@ def uDot(self, t, u, postProcessing=False): vzB = a13 * vx + a23 * vy + a33 * vz # Calculate lift and moment for each component of the rocket for aerodynamicSurface in self.rocket.aerodynamicSurfaces: - compCp = aerodynamicSurface["cp"][2] + compCp = aerodynamicSurface.cp[2] # Component absolute velocity in body frame compVxB = vxB + compCp * omega2 compVyB = vyB - compCp * omega1 @@ -1413,6 +1413,7 @@ def uDot(self, t, u, postProcessing=False): if -1 * compStreamVzBn < 1: compAttackAngle = np.arccos(-compStreamVzBn) cLift = aerodynamicSurface["cl"](compAttackAngle, freestreamMach) + cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) # Component lift force magnitude compLift = ( 0.5 * rho * (compStreamSpeed**2) * self.rocket.area * cLift @@ -1428,8 +1429,8 @@ def uDot(self, t, u, postProcessing=False): M1 -= (compCp + a) * compLiftYB M2 += (compCp + a) * compLiftXB # Calculates Roll Moment - if aerodynamicSurface["name"] == "Fins": - Clfdelta, Cldomega, cantAngleRad = aerodynamicSurface["roll parameters"] + if aerodynamicSurface.name == "Fins": + Clfdelta, Cldomega, cantAngleRad = aerodynamicSurface.rollParameters M3f = ( (1 / 2 * rho * freestreamSpeed**2) * self.rocket.area diff --git a/rocketpy/Rocket.py b/rocketpy/Rocket.py index 7a7fb3441..fdd4ea15d 100644 --- a/rocketpy/Rocket.py +++ b/rocketpy/Rocket.py @@ -13,6 +13,7 @@ from .Function import Function from .Parachute import Parachute +from .AeroSurfaces import NoseCone, TrapezoidalFins, EllipticalFins, Tail class Rocket: @@ -314,13 +315,13 @@ def evaluateStaticMargin(self): if len(self.aerodynamicSurfaces) > 0: for aerodynamicSurface in self.aerodynamicSurfaces: self.totalLiftCoeffDer += Function( - lambda alpha: aerodynamicSurface["cl"](alpha, 0) + lambda alpha: aerodynamicSurface.cl(alpha, 0) ).differentiate(x=1e-2, dx=1e-3) self.cpPosition += ( Function( - lambda alpha: aerodynamicSurface["cl"](alpha, 0) + lambda alpha: aerodynamicSurface.cl(alpha, 0) ).differentiate(x=1e-2, dx=1e-3) - * aerodynamicSurface["cp"][2] + * aerodynamicSurface.cp[2] ) self.cpPosition /= self.totalLiftCoeffDer @@ -335,7 +336,9 @@ def evaluateStaticMargin(self): # Return self return self - def addTail(self, topRadius, bottomRadius, length, distanceToCM): + def addTail( + self, topRadius, bottomRadius, length, distanceToCM, radius=None, name="Tail" + ): """Create a new tail or rocket diameter change, storing its parameters as part of the aerodynamicSurfaces list. Its parameters are the axial position along the rocket and its @@ -369,28 +372,14 @@ def addTail(self, topRadius, bottomRadius, length, distanceToCM): self : Rocket Object of the Rocket class. """ - # Calculate ratio between top and bottom radius - r = topRadius / bottomRadius - # Retrieve reference radius - rref = self.radius + # Modify reference radius if not provided + radius = self.radius if radius is None else radius - # Calculate cp position relative to cm - if distanceToCM < 0: - cpz = distanceToCM - (length / 3) * (1 + (1 - r) / (1 - r**2)) - else: - cpz = distanceToCM + (length / 3) * (1 + (1 - r) / (1 - r**2)) - - # Calculate clalpha - clalpha = -2 * (1 - r ** (-2)) * (topRadius / rref) ** 2 - cl = Function( - lambda alpha, mach: clalpha * alpha, - ["Alpha (rad)", "Mach"], - "Cl", - ) + # Create new tail as an object of the Tail class + tail = Tail(topRadius, bottomRadius, length, distanceToCM, radius, name) - # Store values as new aerodynamic surface - tail = {"cp": (0, 0, cpz), "cl": cl, "name": "Tail"} + # Add tail to aerodynamic surfaces list self.aerodynamicSurfaces.append(tail) # Refresh static margin calculation @@ -399,7 +388,7 @@ def addTail(self, topRadius, bottomRadius, length, distanceToCM): # Return self return self.aerodynamicSurfaces[-1] - def addNose(self, length, kind, distanceToCM): + def addNose(self, length, kind, distanceToCM, name="Nose Cone"): """Creates a nose cone, storing its parameters as part of the aerodynamicSurfaces list. Its parameters are the axial position along the rocket and its derivative of the coefficient of lift @@ -419,6 +408,8 @@ def addNose(self, length, kind, distanceToCM): mass, considering positive direction from center of mass to nose cone. Consider the center point belonging to the nose cone base to calculate distance. + name : string + Nose cone name. Default is "Nose Cone". Returns ------- @@ -432,28 +423,9 @@ def addNose(self, length, kind, distanceToCM): self : Rocket Object of the Rocket class. """ - # Analyze type - if kind == "conical": - k = 1 - 1 / 3 - elif kind == "ogive": - k = 1 - 0.534 - elif kind == "lvhaack": - k = 1 - 0.437 - else: - k = 0.5 - # Calculate cp position relative to cm - cpz = distanceToCM + np.sign(distanceToCM) * k * length - - # Calculate clalpha - clalpha = 2 - cl = Function( - lambda alpha, mach: clalpha * alpha, - ["Alpha (rad)", "Mach"], - "Cl", - ) - - # Store values - nose = {"cp": (0, 0, cpz), "cl": cl, "name": "Nose Cone"} + # Create a nose as an object of NoseCone class + nose = NoseCone(length, kind, distanceToCM, name) + # Add nose to the list of aerodynamic surfaces self.aerodynamicSurfaces.append(nose) # Refresh static margin calculation @@ -486,6 +458,7 @@ def addTrapezoidalFins( sweepAngle=None, radius=None, airfoil=None, + name="Fins", ): """Create a trapezoidal fin set, storing its parameters as part of the aerodynamicSurfaces list. Its parameters are the axial position @@ -551,182 +524,27 @@ def addTrapezoidalFins( self : Rocket Object of the Rocket class. """ - # Retrieves and convert basic geometrical parameters - Cr, Ct = rootChord, tipChord - s = span - radius = self.radius if radius is None else radius - cantAngleRad = np.radians(cantAngle) - - # Check if sweep angle or sweep length is given - if sweepLength is not None and sweepAngle is not None: - raise ValueError("Cannot use sweepLength and sweepAngle together") - elif sweepAngle is not None: - sweepLength = np.tan(sweepAngle * np.pi / 180) * span - elif sweepLength is None: - sweepLength = Cr - Ct - else: - # Sweep length is given - pass - - # Compute auxiliary geometrical parameters - d = 2 * radius - Aref = np.pi * radius**2 - Yr = Cr + Ct - Af = Yr * s / 2 # Fin area - AR = 2 * s**2 / Af # Fin aspect ratio - gamma_c = np.arctan( - (sweepLength + 0.5 * Ct - 0.5 * Cr) / (span) - ) # Mid chord angle - Yma = (s / 3) * (Cr + 2 * Ct) / Yr # Span wise coord of mean aero chord - - rollGeometricalConstant = ( - (Cr + 3 * Ct) * s**3 - + 4 * (Cr + 2 * Ct) * radius * s**2 - + 6 * (Cr + Ct) * s * radius**2 - ) / 12 - - # Center of pressure position relative to CDM (center of dry mass) - cpz = distanceToCM + np.sign(distanceToCM) * ( - (sweepLength / 3) * ((Cr + 2 * Ct) / (Cr + Ct)) - + (1 / 6) * (Cr + Ct - Cr * Ct / (Cr + Ct)) - ) - # Fin–body interference correction parameters - tau = (s + radius) / radius - liftInterferenceFactor = 1 + 1 / tau - λ = Ct / Cr - - # Defines beta parameter - def beta(mach): - """Defines a parameter that is commonly used in aerodynamic - equations. It is commonly used in the Prandtl factor which - corrects subsonic force coefficients for compressible flow. - - Parameters - ---------- - mach : int, float - Number of mach. - - Returns - ------- - beta : int, float - Value that characterizes flow speed based on the mach number. - """ - - if mach < 0.8: - return np.sqrt(1 - mach**2) - elif mach < 1.1: - return np.sqrt(1 - 0.8**2) - else: - return np.sqrt(mach**2 - 1) - - # Defines number of fins factor - def finNumCorrection(n): - """Calculates a correction factor for the lift coefficient of multiple fins. - The specifics values are documented at: - Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development - of an Open Source model rocket simulation software. - - Parameters - ---------- - n : int - Number of fins. - - Returns - ------- - Corrector factor : int - Factor that accounts for the number of fins. - """ - correctorFactor = [2.37, 2.74, 2.99, 3.24] - if n >= 5 and n <= 8: - return correctorFactor[n - 5] - else: - return n / 2 - - if not airfoil: - # Defines clalpha2D as 2*pi for planar fins - clalpha2D = Function(lambda mach: 2 * np.pi / beta(mach)) - else: - # Defines clalpha2D as the derivative of the - # lift coefficient curve for a specific airfoil - airfoilCl = Function( - airfoil[0], - interpolation="linear", - ) - - # Differentiating at x = 0 to get cl_alpha - clalpha2D_Mach0 = airfoilCl.differentiate(x=1e-3, dx=1e-3) - - # Convert to radians if needed - if airfoil[1] == "degrees": - clalpha2D_Mach0 *= 180 / np.pi - - # Correcting for compressible flow - clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) - - # Diederich's Planform Correlation Parameter - FD = 2 * np.pi * AR / (clalpha2D * np.cos(gamma_c)) - - # Lift coefficient derivative for a single fin - clalphaSingleFin = Function( - lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref) * np.cos(gamma_c)) - / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) + # Modify radius if not given, use rocket radius, otherwise use given. + radius = radius if radius is not None else self.radius + + # Create a fin set as an object of TrapezoidalFins class + finSet = TrapezoidalFins( + n, + rootChord, + tipChord, + span, + distanceToCM, + cantAngle, + sweepLength, + sweepAngle, + radius, + airfoil, + name, ) - # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference - clalphaMultipleFins = ( - liftInterferenceFactor * finNumCorrection(n) * clalphaSingleFin - ) # Function of mach number - - # Calculates clalpha * alpha - cl = Function( - lambda alpha, mach: alpha * clalphaMultipleFins(mach), - ["Alpha (rad)", "Mach"], - "Cl", - ) - - # Parameters for Roll Moment. - # Documented at: https://github.com/Projeto-Jupiter/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf - rollDampingInterferenceFactor = 1 + ( - ((tau - λ) / (tau)) - ((1 - λ) / (tau - 1)) * np.log(tau) - ) / ( - ((tau + 1) * (tau - λ)) / (2) - ((1 - λ) * (tau**3 - 1)) / (3 * (tau - 1)) - ) - rollForcingInterferenceFactor = (1 / np.pi**2) * ( - (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) - + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) - + ((tau**2 + 1) ** 2) - / (tau**2 * (tau - 1) ** 2) - * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 - - (4 * (tau + 1)) - / (tau * (tau - 1)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) - ) - clfDelta = ( - rollForcingInterferenceFactor * n * (Yma + radius) * clalphaSingleFin / d - ) # Function of mach number - cldOmega = ( - 2 - * rollDampingInterferenceFactor - * n - * clalphaSingleFin - * np.cos(cantAngleRad) - * rollGeometricalConstant - / (Aref * d**2) - ) # Function of mach number - rollParameters = [clfDelta, cldOmega, cantAngleRad] - - # Store values - fin = { - "cp": (0, 0, cpz), - "cl": cl, - "roll parameters": rollParameters, - "name": "Fins", - } - self.aerodynamicSurfaces.append(fin) + # Add fin set to the list of aerodynamic surfaces + self.aerodynamicSurfaces.append(finSet) # Refresh static margin calculation self.evaluateStaticMargin() @@ -743,6 +561,7 @@ def addEllipticalFins( cantAngle=0, radius=None, airfoil=None, + name="Fins", ): """Create an elliptical fin set, storing its parameters as part of the aerodynamicSurfaces list. Its parameters are the axial position @@ -796,175 +615,17 @@ def addEllipticalFins( self : Rocket Object of the Rocket class. """ - # Retrieves and convert basic geometrical parameters - Cr = rootChord - s = span - radius = self.radius if radius is None else radius - cantAngleRad = np.radians(cantAngle) - - # Compute auxiliary geometrical parameters - d = 2 * radius - Aref = np.pi * radius**2 # Reference area for coefficients - Af = (np.pi * Cr / 2 * s) / 2 # Fin area - AR = 2 * s**2 / Af # Fin aspect ratio - Yma = ( - s / (3 * np.pi) * np.sqrt(9 * np.pi**2 - 16) - ) # Span wise coord of mean aero chord - rollGeometricalConstant = ( - Cr - * s - * (3 * np.pi * s**2 + 32 * radius * s + 12 * np.pi * radius**2) - / 48 - ) - # Center of pressure position relative to CDM (center of dry mass) - cpz = distanceToCM + np.sign(distanceToCM) * (0.288 * Cr) - - # Fin–body interference correction parameters - tau = (s + radius) / radius - liftInterferenceFactor = 1 + 1 / tau - rollDampingInterferenceFactor = 1 + ( - (radius**2) - * ( - 2 - * (radius**2) - * np.sqrt(s**2 - radius**2) - * np.log((2 * s * np.sqrt(s**2 - radius**2) + 2 * s**2) / radius) - - 2 * (radius**2) * np.sqrt(s**2 - radius**2) * np.log(2 * s) - + 2 * s**3 - - np.pi * radius * s**2 - - 2 * (radius**2) * s - + np.pi * radius**3 - ) - ) / (2 * (s**2) * (s / 3 + np.pi * radius / 4) * (s**2 - radius**2)) - rollForcingInterferenceFactor = (1 / np.pi**2) * ( - (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) - + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) - + ((tau**2 + 1) ** 2) - / (tau**2 * (tau - 1) ** 2) - * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 - - (4 * (tau + 1)) - / (tau * (tau - 1)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) - ) - - # Auxiliary functions - # Defines beta parameter - def beta(mach): - """Defines a parameter that is commonly used in aerodynamic - equations. It is commonly used in the Prandtl factor which - corrects subsonic force coefficients for compressible flow. - - Parameters - ---------- - mach : int, float - Number of mach. - - Returns - ------- - beta : int, float - Value that characterizes flow speed based on the mach number. - """ - - if mach < 0.8: - return np.sqrt(1 - mach**2) - elif mach < 1.1: - return np.sqrt(1 - 0.8**2) - else: - return np.sqrt(mach**2 - 1) - - # Defines number of fins correction - def finNumCorrection(n): - """Calculates a corrector factor for the lift coefficient of multiple fins. - The specifics values are documented at: - Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development - of an Open Source model rocket simulation software. - - Parameters - ---------- - n : int - Number of fins. - - Returns - ------- - Corrector factor : int - Factor that accounts for the number of fins. - """ - correctorFactor = [2.37, 2.74, 2.99, 3.24] - if n >= 5 and n <= 8: - return correctorFactor[n - 5] - else: - return n / 2 + # Modify radius if not given, use rocket radius, otherwise use given. + radius = radius if radius is not None else self.radius - if not airfoil: - # Defines clalpha2D as 2*pi for planar fins - clalpha2D = Function(lambda mach: 2 * np.pi / beta(mach)) - else: - # Defines clalpha2D as the derivative of the - # lift coefficient curve for a specific airfoil - airfoilCl = Function( - airfoil[0], - interpolation="linear", - ) - - # Differentiating at x = 0 to get cl_alpha - clalpha2D_Mach0 = airfoilCl.differentiate(x=1e-3, dx=1e-3) - - # Convert to radians if needed - if airfoil[1] == "degrees": - clalpha2D_Mach0 *= 180 / np.pi - - # Correcting for compressible flow - clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) - # Diederich's Planform Correlation Parameter - FD = 2 * np.pi * AR / (clalpha2D) - - # Lift coefficient derivative for a single fin - clalphaSingleFin = Function( - lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref)) - / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) + # Create a fin set as an object of EllipticalFins class + finSet = EllipticalFins( + n, rootChord, span, distanceToCM, cantAngle, radius, airfoil, name ) - # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference - clalphaMultipleFins = ( - liftInterferenceFactor * finNumCorrection(n) * clalphaSingleFin - ) # Function of mach number - - # Calculates clalpha * alpha - cl = Function( - lambda alpha, mach: alpha * clalphaMultipleFins(mach), - ["Alpha (rad)", "Mach"], - "Cl", - ) - - # Parameters for Roll Moment. - # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/develop/docs/technical/aerodynamics/Roll_Equations.pdf - clfDelta = ( - rollForcingInterferenceFactor * n * (Yma + radius) * clalphaSingleFin / d - ) # Function of mach number - cldOmega = ( - 2 - * rollDampingInterferenceFactor - * n - * clalphaSingleFin - * np.cos(cantAngleRad) - * rollGeometricalConstant - / (Aref * d**2) - ) - # Function of mach number - rollParameters = [clfDelta, cldOmega, cantAngleRad] - - # Store values - fin = { - "cp": (0, 0, cpz), - "cl": cl, - "roll parameters": rollParameters, - "name": "Fins", - } - self.aerodynamicSurfaces.append(fin) + # Add fin set to the list of aerodynamic surfaces + self.aerodynamicSurfaces.append(finSet) # Refresh static margin calculation self.evaluateStaticMargin() @@ -1063,7 +724,7 @@ def setRailButtons(self, distanceToCM, angularPosition=45): # Order distance to CM if distanceToCM[0] < distanceToCM[1]: distanceToCM.reverse() - # Save + # Save important attributes self.railButtons = self.railButtonPair(distanceToCM, angularPosition) return None @@ -1239,9 +900,9 @@ def allInfo(self): # Print rocket aerodynamics quantities print("\nAerodynamics Lift Coefficient Derivatives") for aerodynamicSurface in self.aerodynamicSurfaces: - name = aerodynamicSurface["name"] + name = aerodynamicSurface.name clalpha = Function( - lambda alpha: aerodynamicSurface["cl"](alpha, 0), + lambda alpha: aerodynamicSurface.cl(alpha, 0), ).differentiate(x=1e-2, dx=1e-3) print( name + " Lift Coefficient Derivative: {:.3f}".format(clalpha) + "/rad" @@ -1249,8 +910,8 @@ def allInfo(self): print("\nAerodynamics Center of Pressure") for aerodynamicSurface in self.aerodynamicSurfaces: - name = aerodynamicSurface["name"] - cpz = aerodynamicSurface["cp"][2] + name = aerodynamicSurface.name + cpz = aerodynamicSurface.cp[2] print(name + " Center of Pressure to CM: {:.3f}".format(cpz) + " m") print( "Distance - Center of Pressure to CM: " From eff4dc0e0f1d7dabdbf4cc1f0b8de3c70af1448c Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 24 Oct 2022 01:09:21 +0200 Subject: [PATCH 02/17] MAINT: adapting attributes names at AeroSurfaces --- rocketpy/AeroSurfaces.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index feb9699b4..5f1baafe5 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -155,18 +155,18 @@ def __init__( """ # Store values self.numberOfFins = n - self.finRadius = radius - self.finAirfoil = airfoil - self.finDistanceToCM = distanceToCM - self.finCantAngle = cantAngle - self.finRootChord = rootChord - self.finTipChord = tipChord - self.finSpan = span + self.radius = radius + self.airfoil = airfoil + self.distanceToCM = distanceToCM + self.cantAngle = cantAngle + self.rootChord = rootChord + self.tipChord = tipChord + self.span = span self.name = name # get some nicknames - Cr, Ct = self.finRootChord, self.finTipChord - s = self.finSpan + Cr, Ct = self.rootChord, self.tipChord + s = self.span cantAngleRad = np.radians(cantAngle) # Check if sweep angle or sweep length is given @@ -618,10 +618,10 @@ def __init__( """ # Store arguments as attributes - self.tailTopRadius = topRadius - self.tailBottomRadius = bottomRadius - self.tailLength = length - self.tailDistanceToCM = distanceToCM + self.topRadius = topRadius + self.bottomRadius = bottomRadius + self.length = length + self.distanceToCM = distanceToCM self.name = name self.radius = radius From 2293c1c81a2bee1b91787542048374a794402df9 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Mon, 24 Oct 2022 02:13:25 +0200 Subject: [PATCH 03/17] FIX: adjust variables names to surpass tests --- rocketpy/AeroSurfaces.py | 16 ++++++++++++---- rocketpy/Flight.py | 2 +- tests/test_rocket.py | 8 ++++---- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 5f1baafe5..85f7db32d 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -163,8 +163,10 @@ def __init__( self.tipChord = tipChord self.span = span self.name = name + self.sweepLength = sweepLength + self.sweepAngle = sweepAngle - # get some nicknames + # Get some nicknames Cr, Ct = self.rootChord, self.tipChord s = self.span cantAngleRad = np.radians(cantAngle) @@ -191,9 +193,15 @@ def __init__( ) # Mid chord angle Yma = (s / 3) * (Cr + 2 * Ct) / Yr # Span wise coord of mean aero chord + rollGeometricalConstant = ( + (Cr + 3 * Ct) * s**3 + + 4 * (Cr + 2 * Ct) * radius * s**2 + + 6 * (Cr + Ct) * s * radius**2 + ) / 12 + # Center of pressure position relative to CDM (center of dry mass) cpz = distanceToCM + np.sign(distanceToCM) * ( - ((Cr - Ct) / 3) * ((Cr + 2 * Ct) / (Cr + Ct)) + (sweepLength / 3) * ((Cr + 2 * Ct) / (Cr + Ct)) + (1 / 6) * (Cr + Ct - Cr * Ct / (Cr + Ct)) ) @@ -271,11 +279,11 @@ def finNumCorrection(n): clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) # Diederich's Planform Correlation Parameter - FD = 2 * np.pi * AR / (clalpha2D * np.cos(gamac)) + FD = 2 * np.pi * AR / (clalpha2D * np.cos(gamma_c)) # Lift coefficient derivative for a single fin clalphaSingleFin = Function( - lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref) * np.cos(gamac)) + lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref) * np.cos(gamma_c)) / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) ) diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py index 172f584ec..3d8b61d77 100644 --- a/rocketpy/Flight.py +++ b/rocketpy/Flight.py @@ -1412,7 +1412,7 @@ def uDot(self, t, u, postProcessing=False): compStreamVzBn = compStreamVzB / compStreamSpeed if -1 * compStreamVzBn < 1: compAttackAngle = np.arccos(-compStreamVzBn) - cLift = aerodynamicSurface["cl"](compAttackAngle, freestreamMach) + cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) # Component lift force magnitude compLift = ( diff --git a/tests/test_rocket.py b/tests/test_rocket.py index 4ece55172..29710c044 100644 --- a/tests/test_rocket.py +++ b/tests/test_rocket.py @@ -361,11 +361,11 @@ def test_add_trapezoidal_fins_sweep_angle( # Check center of pressure translate = 0.55829 + 0.71971 - cpz = FinSet["cp"][2] + cpz = FinSet.cp[2] assert translate - cpz == pytest.approx(expected_fin_cpz, 0.01) # Check lift coefficient derivative - cl_alpha = FinSet["cl"](1, 0.0) + cl_alpha = FinSet.cl(1, 0.0) assert cl_alpha == pytest.approx(expected_clalpha, 0.01) # Check rocket's center of pressure (just double checking) @@ -393,11 +393,11 @@ def test_add_trapezoidal_fins_sweep_length( # Check center of pressure translate = 0.55829 + 0.71971 - cpz = FinSet["cp"][2] + cpz = FinSet.cp[2] assert translate - cpz == pytest.approx(expected_fin_cpz, 0.01) # Check lift coefficient derivative - cl_alpha = FinSet["cl"](1, 0.0) + cl_alpha = FinSet.cl(1, 0.0) assert cl_alpha == pytest.approx(expected_clalpha, 0.01) # Check rocket's center of pressure (just double checking) From ea45c551a0a7370e27de2567a9552beaef40a2cd Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 05:34:48 -0300 Subject: [PATCH 04/17] maint: fixed wrong Yma formula in docs --- .../aerodynamics/Elliptical_Fins.pdf | Bin 301609 -> 301620 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/technical/aerodynamics/Elliptical_Fins.pdf b/docs/technical/aerodynamics/Elliptical_Fins.pdf index 9c36933fb400ed5cf0a9bebe4a30889443cd48fa..6185d34b0efd210bb2b4db5ae25c69e16e9e3426 100644 GIT binary patch delta 1597 zcmbu9YdF&j0EgRVZe^RJns$l)j!0Z=qhj6Mv-{4Z>wE7OoqE60 z^YXpO@s{j06~{wJ{QKves(Y;dxrRBTw`x&s8kOeml#_pcF4}^O%I%onhFV`tpMz)5 z!683^a_8VMunLO8gNY3=0pu0eodw$ur$tfw>v~i(8MSEeqtzLa~i62vliJ^ym zN){g9R;N>-j;pJ*0^661gx|gyhc}+kFl3GitC#TeM7BWp^ z$FRbIAl+@-{>#bV%YY%CV-9W6pAnbcH*e=&AL-1NSoatD`TfzCl78EBkJ%MNH@T+S z*1YwS4NF7nRB&Bz*5oMzlNrf%ej#`7zIky^{cVM6{<`$tgw>l-j#I}#7n53j-d9;h zXa#CKQ)yq^XlWyeD3*;x?w)r*uhqG=$Q_5C7Bi;t2zt|@D)C= z?~Stw*%NRNv3Az-iMA~dlVDYym32+h_xob6`tA_rm9hhDx$~{7{4K%7ZL0m`{M+n< za~sjzkhNn+_3*uQ?b~4p8->gGu;H*qf6s4rNj&DL(rFdOE$y|G=~E7ov=;HM&*iyA z{#W3id)S$I1y^-->;h@1{K#67Fb^Ag^5qAO<_z2ls!px{{28yEGO*>$fG=6a79od{ zL~VYWW!0hb%QKViv2vKDqSU^eiHGh{IqAE~jpEhGs&e<2M|7o6uysgJ)`i=vM3Iya zulz%Nr?>Bs!ATJ_huKBF)5#p_*RD~hNY~CE!4Ah~0VjG#yl6g!HA2AfW1>uw&bTz9 zUQnwfX$JoG^l3c05#BnYHOT)|V6^=%r`tMv3>v$$Zl%p9Va>W>12CpW1ZvjDlVr*S z@ET)5KAV53)8B$F@TmK$%dA2oXt;z)vM%#ztun_cazB^uN-C(2}v6*r=C?%ae9@HGN8kvq$x2ko1kB};Od-OC>^Vq zsZaXyIL`9UOFTKR%XO!3jkEEVr0{wEQcveQzeC0%EqT3%k_6S0*X3R;7 zP`2Bx(y58i0>9Z30w)f6?4UN)HF=Uf>Ls|RTWt6;}w<}94FSRZVQ%po& z8ok)^E=o>vcg`3Y)sTv6>bRzQY}cRn+`spIp3fhjzdz6O6^jiU#D@6*1RxZk?IC#l zUHVSVZ~#w0{$~zeL}a)NmWd}2n0PePDH=<_p&c1m6dgyvU_d+*hXGL7A6(FmXeOh`;vKP z<-Wvry|EQfntoVVLkg?U+P!KRQFEcW(IE$ZTH<_KbSjNILe^(5JeX~HcCRd`?0IIX z_9b@PzPQxQ*ZVClqi)&NnO>eq2%IZkH{M-4RdILj*`9^$&N6qL%e3-M;@F1xc;4ZU znNpv%twwZ+sY1d2W$?v(u=l);cAatW?M*a5TD@{vdR*UK!1~oPL&aOB%9irZo!gTz zhLbYGIKv#8vRr9DlYcGGxazje4!!yls;gfBzn+-3ko=|=- z?Rl7|Lx}Qx#O@L>t-depwbjQ_8y{MhuN~uvxK9l7u{zDU#+@%hB0QEg#sjgp*nZjN z`kP_FO|f6ED&p7uDcZ0_zn;5ow&z1wIxuj+nB-iU&4QE*oy96~q`KViQ(IM)c3bz9 z{lwRiTb>lzvA7;r4z*9k4G~Q&NJlgCpC?tV5M`u!)?;V66U}GR!o-Es^~&<4$&swC z+pKNX42N8@U+_W3mHMA-<}L-YgT(QkXOWck`;!@_6bNf3DyrK+_#9}?)`ickvi_#8 zm!;o%E_n8)M4r15VeN+a?D)RMY7^%CFC057clxvRM`yf)z{b-19XXM|U3FUw3lLCn ztT7T{DkC5vDYYdiH7Q+gC9V?L*NlAjDE$MTZSxf2y@(QlmuG~ zQ>M*L_PX-<_X23cFm~~K^ugf@wO)JD#+*k5q0UK4H3l$zNDF>bQTJKds;Y@yuqhyj0mx5D`XVxDjAx-^+%|p zCgTU)nMp0Hdl&Bw#&k{uwIUn@s-Aqflw~&{#fQ|rvRQP{VpGDZaipT|lbGP3s2%gQ z&>zjRysuO|JY;*ZND^x6+Jx!PY6BB9c*W%HY1jNJCu!6zs?R_pSmZKj{b2-2xqb(1 zG!LcZ0DE(|3Ah-#%@~=a9!0fJPKPL`bx8eY#H{T~HKf`Gm3761#1H;-if=+~ADnW3 zLOaPVp@%#ce<_UaT}qtx-82NOGdF({{pNzjR?N1-UEhetn_3=rBa1e+r}&COiZ{-WF?pg~aW|Se1fq`E~|LnHUWX)r_GL&j(V>x3~&3 F`3L8$)RF)I From d898773b7e23480d757f7bcea4adb46c35cbd624 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 05:39:34 -0300 Subject: [PATCH 05/17] enh: added super Fin class and reorganized other fin classes --- rocketpy/AeroSurfaces.py | 703 ++++++++++++++++----------------------- 1 file changed, 280 insertions(+), 423 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 85f7db32d..bbb29c896 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -3,6 +3,8 @@ __license__ = "MIT" import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Ellipse from .Function import Function @@ -85,186 +87,16 @@ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): return None -class TrapezoidalFins: - """Keeps trapezoidal fins information. - - Attributes - ---------- - - """ - - def __init__( - self, - n, - rootChord, - tipChord, - span, - distanceToCM, - cantAngle=0, - sweepLength=None, - sweepAngle=None, - radius=None, - airfoil=None, - name="Fins", - ): - """Initializes the trapezoidal fins. It is used to define the number of - fins, root chord, tip chord, span, distance to center of mass, cant angle - and name. - - Parameters - ---------- - n : int - Number of fins, from 2 to infinity. - span : int, float - Fin span in meters. - rootChord : int, float - Fin root chord in meters. - tipChord : int, float - Fin tip chord in meters. - distanceToCM : int, float - Fin set position relative to rocket unloaded center of - mass, considering positive direction from center of mass to - nose cone. Consider the center point belonging to the top - of the fins to calculate distance. - cantAngle : int, float, optional - Fins cant angle with respect to the rocket centerline. Must - be given in degrees. - radius : int, float, optional - Reference radius to calculate lift coefficient. If None, which - is default, use rocket radius. Otherwise, enter the radius - of the rocket in the section of the fins, as this impacts - its lift coefficient. - airfoil : tuple, optional - Default is null, in which case fins will be treated as flat plates. - Otherwise, if tuple, fins will be considered as airfoils. The - tuple's first item specifies the airfoil's lift coefficient - by angle of attack and must be either a .csv, .txt, ndarray - or callable. The .csv and .txt files must contain no headers - and the first column must specify the angle of attack, while - the second column must specify the lift coefficient. The - ndarray should be as [(x0, y0), (x1, y1), (x2, y2), ...] - where x0 is the angle of attack and y0 is the lift coefficient. - If callable, it should take an angle of attack as input and - return the lift coefficient at that angle of attack. - The tuple's second item is the unit of the angle of attack, - accepting either "radians" or "degrees". - - Returns - ------- - None - """ - # Store values - self.numberOfFins = n - self.radius = radius - self.airfoil = airfoil - self.distanceToCM = distanceToCM - self.cantAngle = cantAngle - self.rootChord = rootChord - self.tipChord = tipChord - self.span = span - self.name = name - self.sweepLength = sweepLength - self.sweepAngle = sweepAngle - - # Get some nicknames - Cr, Ct = self.rootChord, self.tipChord - s = self.span - cantAngleRad = np.radians(cantAngle) - - # Check if sweep angle or sweep length is given - if sweepLength is not None and sweepAngle is not None: - raise ValueError("Cannot use sweepLength and sweepAngle together") - elif sweepAngle is not None: - sweepLength = np.tan(sweepAngle * np.pi / 180) * span - elif sweepLength is None: - sweepLength = Cr - Ct - else: - # Sweep length is given - pass - - # Compute auxiliary geometrical parameters - d = 2 * radius - Aref = np.pi * radius**2 - Yr = Cr + Ct - Af = Yr * s / 2 # Fin area - AR = 2 * s**2 / Af # Fin aspect ratio - gamma_c = np.arctan( - (sweepLength + 0.5 * Ct - 0.5 * Cr) / (span) - ) # Mid chord angle - Yma = (s / 3) * (Cr + 2 * Ct) / Yr # Span wise coord of mean aero chord - - rollGeometricalConstant = ( - (Cr + 3 * Ct) * s**3 - + 4 * (Cr + 2 * Ct) * radius * s**2 - + 6 * (Cr + Ct) * s * radius**2 - ) / 12 - - # Center of pressure position relative to CDM (center of dry mass) - cpz = distanceToCM + np.sign(distanceToCM) * ( - (sweepLength / 3) * ((Cr + 2 * Ct) / (Cr + Ct)) - + (1 / 6) * (Cr + Ct - Cr * Ct / (Cr + Ct)) - ) - - # Fin–body interference correction parameters - tau = (s + radius) / radius - liftInterferenceFactor = 1 + 1 / tau - λ = Ct / Cr - - # Defines beta parameter - def beta(mach): - """Defines a parameter that is commonly used in aerodynamic - equations. It is commonly used in the Prandtl factor which - corrects subsonic force coefficients for compressible flow. - - Parameters - ---------- - mach : int, float - Number of mach. - - Returns - ------- - beta : int, float - Value that characterizes flow speed based on the mach number. - """ - - if mach < 0.8: - return np.sqrt(1 - mach**2) - elif mach < 1.1: - return np.sqrt(1 - 0.8**2) - else: - return np.sqrt(mach**2 - 1) - - # Defines number of fins factor - def finNumCorrection(n): - """Calculates a correction factor for the lift coefficient of multiple fins. - The specifics values are documented at: - Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development - of an Open Source model rocket simulation software. - - Parameters - ---------- - n : int - Number of fins. - - Returns - ------- - Corrector factor : int - Factor that accounts for the number of fins. - """ - correctorFactor = [2.37, 2.74, 2.99, 3.24] - if n >= 5 and n <= 8: - return correctorFactor[n - 5] - else: - return n / 2 - - if not airfoil: +class Fins: + def evaluateLiftCoefficient(self): + if not self.airfoil: # Defines clalpha2D as 2*pi for planar fins - clalpha2D = Function(lambda mach: 2 * np.pi / beta(mach)) + clalpha2D = Function(lambda mach: 2 * np.pi / self._beta(mach)) else: # Defines clalpha2D as the derivative of the # lift coefficient curve for a specific airfoil airfoilCl = Function( - airfoil[0], + self.airfoil[0], interpolation="linear", ) @@ -272,323 +104,348 @@ def finNumCorrection(n): clalpha2D_Mach0 = airfoilCl.differentiate(x=1e-3, dx=1e-3) # Convert to radians if needed - if airfoil[1] == "degrees": + if self.airfoil[1] == "degrees": clalpha2D_Mach0 *= 180 / np.pi # Correcting for compressible flow - clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) + clalpha2D = Function(lambda mach: clalpha2D_Mach0 / self._beta(mach)) # Diederich's Planform Correlation Parameter - FD = 2 * np.pi * AR / (clalpha2D * np.cos(gamma_c)) + FD = 2 * np.pi * self.AR / (clalpha2D * np.cos(self.gamma_c)) # Lift coefficient derivative for a single fin - clalphaSingleFin = Function( - lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref) * np.cos(gamma_c)) + self.clalphaSingleFin = Function( + lambda mach: ( + clalpha2D(mach) + * FD(mach) + * (self.Af / self.Aref) + * np.cos(self.gamma_c) + ) / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) ) # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference - clalphaMultipleFins = ( - liftInterferenceFactor * finNumCorrection(n) * clalphaSingleFin + self.clalphaMultipleFins = ( + self.liftInterferenceFactor + * self._finNumCorrection(self.n) + * self.clalphaSingleFin ) # Function of mach number # Calculates clalpha * alpha - cl = Function( - lambda alpha, mach: alpha * clalphaMultipleFins(mach), + self.cl = Function( + lambda alpha, mach: alpha * self.clalphaMultipleFins(mach), ["Alpha (rad)", "Mach"], "Cl", ) - # Parameters for Roll Moment. - # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf - rollDampingInterferenceFactor = 1 + ( - ((tau - λ) / (tau)) - ((1 - λ) / (tau - 1)) * np.log(tau) - ) / ( - ((tau + 1) * (tau - λ)) / (2) - ((1 - λ) * (tau**3 - 1)) / (3 * (tau - 1)) - ) - rollForcingInterferenceFactor = (1 / np.pi**2) * ( - (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) - + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) - + ((tau**2 + 1) ** 2) - / (tau**2 * (tau - 1) ** 2) - * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 - - (4 * (tau + 1)) - / (tau * (tau - 1)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) - ) + return self + + def evaluateRollCoefficients(self): clfDelta = ( - rollForcingInterferenceFactor * n * (Yma + radius) * clalphaSingleFin / d + self.rollForcingInterferenceFactor + * self.n + * (self.Yma + self.radius) + * self.clalphaSingleFin + / self.d ) # Function of mach number cldOmega = ( 2 - * rollDampingInterferenceFactor - * n - * clalphaSingleFin - * np.cos(cantAngleRad) - * rollGeometricalConstant - / (Aref * d**2) + * self.rollDampingInterferenceFactor + * self.n + * self.clalphaSingleFin + * np.cos(self.cantAngleRad) + * self.rollGeometricalConstant + / (self.Aref * self.d**2) ) # Function of mach number - rollParameters = [clfDelta, cldOmega, cantAngleRad] + self.rollParameters = [clfDelta, cldOmega, self.cantAngleRad] + return self - # Save and store parameters - self.rollParameters = rollParameters - self.cl = cl - self.clalphaSingleFin = clalphaSingleFin - self.clalphaMultipleFins = clalphaMultipleFins - self.cpx = 0 - self.cpy = 0 - self.cpz = cpz - self.cp = (self.cpx, self.cpy, self.cpz) + def changeCantAngle(self, cantAngle): + self.cantAngleList.append(cantAngle) - def info(self): - "Still not implemented. Must print important information." + self.cantAngle = cantAngle + self.cantAngleRad = np.radians(cantAngle) - return None + self.evaluateRollCoefficients() - return None + return self + # Defines beta parameter + def _beta(_, mach): + """Defines a parameter that is commonly used in aerodynamic + equations. It is commonly used in the Prandtl factor which + corrects subsonic force coefficients for compressible flow. -class EllipticalFins: - """Class that defines the aerodynamic model of an elliptical fin. + Parameters + ---------- + mach : int, float + Number of mach. - Parameters - ---------- - n : int - Number of fins. - radius : int, float - Fin radius. - span : int, float - Fin span. - cantAngle : int, float - Cant angle of the fin. - - Returns - ------- - None - """ + Returns + ------- + beta : int, float + Value that characterizes flow speed based on the mach number. + """ + + if mach < 0.8: + return np.sqrt(1 - mach**2) + elif mach < 1.1: + return np.sqrt(1 - 0.8**2) + else: + return np.sqrt(mach**2 - 1) + + # Defines number of fins factor + def _finNumCorrection(_, n): + """Calculates a correction factor for the lift coefficient of multiple fins. + The specifics values are documented at: + Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development + of an Open Source model rocket simulation software. + + Parameters + ---------- + n : int + Number of fins. + + Returns + ------- + Corrector factor : int + Factor that accounts for the number of fins. + """ + correctorFactor = [2.37, 2.74, 2.99, 3.24] + if n >= 5 and n <= 8: + return correctorFactor[n - 5] + else: + return n / 2 + +class TrapezoidalFins(Fins): def __init__( self, n, rootChord, + tipChord, span, distanceToCM, + radius, cantAngle=0, - radius=None, + sweepLength=None, + sweepAngle=None, airfoil=None, name="Fins", ): - """Initializes the class, defining the parameters of the fins. - Parameters - ---------- - n : int - Number of fins. - rootChord : _type_ - _description_ - span : _type_ - _description_ - distanceToCM : _type_ - _description_ - cantAngle : int, optional - _description_, by default 0 - radius : _type_, optional - _description_, by default None - airfoil : _type_, optional - _description_, by default None - name : str, optional - _description_, by default "Fins" + # Check if sweep angle or sweep length is given + if sweepLength is not None and sweepAngle is not None: + raise ValueError("Cannot use sweepLength and sweepAngle together") + elif sweepAngle is not None: + sweepLength = np.tan(sweepAngle * np.pi / 180) * span + elif sweepLength is None: + sweepLength = rootChord - tipChord + else: + # Sweep length is given + pass - Returns - ------- - None + # Compute auxiliary geometrical parameters + d = 2 * radius + Aref = np.pi * radius**2 # Reference area + Yr = rootChord + tipChord + Af = Yr * span / 2 # Fin area + AR = 2 * span**2 / Af # Fin aspect ratio + gamma_c = np.arctan( + (sweepLength + 0.5 * tipChord - 0.5 * rootChord) / (span) + ) # Mid chord angle + Yma = ( + (span / 3) * (rootChord + 2 * tipChord) / Yr + ) # Span wise coord of mean aero chord - """ + rollGeometricalConstant = ( + (rootChord + 3 * tipChord) * span**3 + + 4 * (rootChord + 2 * tipChord) * radius * span**2 + + 6 * (rootChord + tipChord) * span * radius**2 + ) / 12 - # Save attributes - self.numberOfFins = n - self.rootChord = rootChord - self.span = span - self.distanceToCM = distanceToCM - self.cantAngle = cantAngle + # Fin–body interference correction parameters + tau = (span + radius) / radius + liftInterferenceFactor = 1 + 1 / tau + + # Store values + self.n = n self.radius = radius self.airfoil = airfoil + self.distanceToCM = distanceToCM + self.cantAngle = cantAngle + self.cantAngleList = [cantAngle] + self.cantAngleRad = np.radians(cantAngle) + self.rootChord = rootChord + self.tipChord = tipChord + self.span = span self.name = name + self.sweepLength = sweepLength + self.sweepAngle = sweepAngle + self.d = d + self.Aref = Aref # Reference area + self.Yr = Yr + self.Af = Af * span / 2 # Fin area + self.AR = AR # Fin aspect ratio + self.gamma_c = gamma_c # Mid chord angle + self.Yma = Yma # Span wise coord of mean aero chord + self.rollGeometricalConstant = rollGeometricalConstant + self.tau = tau + self.liftInterferenceFactor = liftInterferenceFactor + + self.evaluateCenterOfPressure() + self.evaluateLiftCoefficient() + + if cantAngle: + # Parameters for Roll Moment. + # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf + self.λ = tipChord / rootChord + self.rollDampingInterferenceFactor = 1 + ( + ((tau - self.λ) / (tau)) - ((1 - self.λ) / (tau - 1)) * np.log(tau) + ) / ( + ((tau + 1) * (tau - self.λ)) / (2) + - ((1 - self.λ) * (tau**3 - 1)) / (3 * (tau - 1)) + ) + self.rollForcingInterferenceFactor = (1 / np.pi**2) * ( + (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) + + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) + + ((tau**2 + 1) ** 2) + / (tau**2 * (tau - 1) ** 2) + * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 + - (4 * (tau + 1)) + / (tau * (tau - 1)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) + ) + self.evaluateRollCoefficients() + + def evaluateCenterOfPressure(self): + # Center of pressure position relative to CDM (center of dry mass) + cpz = self.distanceToCM + np.sign(self.distanceToCM) * ( + (self.sweepLength / 3) + * ((self.rootChord + 2 * self.tipChord) / (self.rootChord + self.tipChord)) + + (1 / 6) + * ( + self.rootChord + + self.tipChord + - self.rootChord * self.tipChord / (self.rootChord + self.tipChord) + ) + ) + self.cpx = 0 + self.cpy = 0 + self.cpz = cpz + self.cp = (self.cpx, self.cpy, self.cpz) + return self.cp - # Get some nicknames - Cr = self.rootChord - s = self.span - cantAngleRad = np.radians(cantAngle) + +class EllipticalFins(Fins): + def __init__( + self, + n, + rootChord, + span, + distanceToCM, + radius, + cantAngle=0, + airfoil=None, + name="Fins", + ): # Compute auxiliary geometrical parameters d = 2 * radius Aref = np.pi * radius**2 # Reference area for coefficients - Af = (np.pi * Cr / 2 * s) / 2 # Fin area - AR = 2 * s**2 / Af # Fin aspect ratio + Af = (np.pi * rootChord / 2 * span) / 2 # Fin area + gamma_c = 0 # Zero for elliptical fins + AR = 2 * span**2 / Af # Fin aspect ratio Yma = ( - s / (3 * np.pi) * np.sqrt(9 * np.pi**2 - 16) + span / (3 * np.pi) * np.sqrt(9 * np.pi**2 - 64) ) # Span wise coord of mean aero chord rollGeometricalConstant = ( - Cr - * s - * (3 * np.pi * s**2 + 32 * radius * s + 12 * np.pi * radius**2) + rootChord + * span + * (3 * np.pi * span**2 + 32 * radius * span + 12 * np.pi * radius**2) / 48 ) - # Center of pressure position relative to CDM (center of dry mass) - cpz = distanceToCM + np.sign(distanceToCM) * (0.288 * Cr) - # Fin–body interference correction parameters - tau = (s + radius) / radius + tau = (span + radius) / radius liftInterferenceFactor = 1 + 1 / tau - rollDampingInterferenceFactor = 1 + ( - (radius**2) - * ( + + # Store values + self.n = n + self.radius = radius + self.airfoil = airfoil + self.distanceToCM = distanceToCM + self.cantAngle = cantAngle + self.cantAngleList = [cantAngle] + self.cantAngleRad = np.radians(cantAngle) + self.rootChord = rootChord + self.span = span + self.name = name + self.d = d + self.Aref = Aref # Reference area + self.Af = Af * span / 2 # Fin area + self.AR = AR # Fin aspect ratio + self.gamma_c = gamma_c # Mid chord angle + self.Yma = Yma # Span wise coord of mean aero chord + self.rollGeometricalConstant = rollGeometricalConstant + self.tau = tau + self.liftInterferenceFactor = liftInterferenceFactor + + self.evaluateCenterOfPressure() + self.evaluateLiftCoefficient() + + if cantAngle: + self.rollDampingInterferenceFactor = 1 + ( + (radius**2) + * ( + 2 + * (radius**2) + * np.sqrt(span**2 - radius**2) + * np.log( + (2 * span * np.sqrt(span**2 - radius**2) + 2 * span**2) + / radius + ) + - 2 + * (radius**2) + * np.sqrt(span**2 - radius**2) + * np.log(2 * span) + + 2 * span**3 + - np.pi * radius * span**2 + - 2 * (radius**2) * span + + np.pi * radius**3 + ) + ) / ( 2 - * (radius**2) - * np.sqrt(s**2 - radius**2) - * np.log((2 * s * np.sqrt(s**2 - radius**2) + 2 * s**2) / radius) - - 2 * (radius**2) * np.sqrt(s**2 - radius**2) * np.log(2 * s) - + 2 * s**3 - - np.pi * radius * s**2 - - 2 * (radius**2) * s - + np.pi * radius**3 + * (span**2) + * (span / 3 + np.pi * radius / 4) + * (span**2 - radius**2) ) - ) / (2 * (s**2) * (s / 3 + np.pi * radius / 4) * (s**2 - radius**2)) - rollForcingInterferenceFactor = (1 / np.pi**2) * ( - (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) - + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) - + ((tau**2 + 1) ** 2) - / (tau**2 * (tau - 1) ** 2) - * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 - - (4 * (tau + 1)) - / (tau * (tau - 1)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) - ) - - # Auxiliary functions - # Defines beta parameter - def beta(mach): - """Defines a parameter that is commonly used in aerodynamic - equations. It is commonly used in the Prandtl factor which - corrects subsonic force coefficients for compressible flow. - - Parameters - ---------- - mach : int, float - Number of mach. - - Returns - ------- - beta : int, float - Value that characterizes flow speed based on the mach number. - """ - - if mach < 0.8: - return np.sqrt(1 - mach**2) - elif mach < 1.1: - return np.sqrt(1 - 0.8**2) - else: - return np.sqrt(mach**2 - 1) - - # Defines number of fins correction - def finNumCorrection(n): - """Calculates a corrector factor for the lift coefficient of multiple fins. - The specifics values are documented at: - Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development - of an Open Source model rocket simulation software. - - Parameters - ---------- - n : int - Number of fins. - - Returns - ------- - Corrector factor : int - Factor that accounts for the number of fins. - """ - correctorFactor = [2.37, 2.74, 2.99, 3.24] - if n >= 5 and n <= 8: - return correctorFactor[n - 5] - else: - return n / 2 - - if not airfoil: - # Defines clalpha2D as 2*pi for planar fins - clalpha2D = Function(lambda mach: 2 * np.pi / beta(mach)) - else: - # Defines clalpha2D as the derivative of the - # lift coefficient curve for a specific airfoil - airfoilCl = Function( - airfoil[0], - interpolation="linear", + self.rollForcingInterferenceFactor = (1 / np.pi**2) * ( + (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) + + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) + + ((tau**2 + 1) ** 2) + / (tau**2 * (tau - 1) ** 2) + * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 + - (4 * (tau + 1)) + / (tau * (tau - 1)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) ) + self.evaluateRollCoefficients() - # Differentiating at x = 0 to get cl_alpha - clalpha2D_Mach0 = airfoilCl.differentiate(x=1e-3, dx=1e-3) - - # Convert to radians if needed - if airfoil[1] == "degrees": - clalpha2D_Mach0 *= 180 / np.pi - - # Correcting for compressible flow - clalpha2D = Function(lambda mach: clalpha2D_Mach0 / beta(mach)) - # Diederich's Planform Correlation Parameter - FD = 2 * np.pi * AR / (clalpha2D) - - # Lift coefficient derivative for a single fin - clalphaSingleFin = Function( - lambda mach: (clalpha2D(mach) * FD(mach) * (Af / Aref)) - / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) - ) - - # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference - clalphaMultipleFins = ( - liftInterferenceFactor * finNumCorrection(n) * clalphaSingleFin - ) # Function of mach number - - # Calculates clalpha * alpha - cl = Function( - lambda alpha, mach: alpha * clalphaMultipleFins(mach), - ["Alpha (rad)", "Mach"], - "Cl", - ) - - # Parameters for Roll Moment. - # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/develop/docs/technical/aerodynamics/Roll_Equations.pdf - clfDelta = ( - rollForcingInterferenceFactor * n * (Yma + radius) * clalphaSingleFin / d - ) # Function of mach number - cldOmega = ( - 2 - * rollDampingInterferenceFactor - * n - * clalphaSingleFin - * np.cos(cantAngleRad) - * rollGeometricalConstant - / (Aref * d**2) - ) - # Function of mach number - rollParameters = [clfDelta, cldOmega, cantAngleRad] - - # Store values + def evaluateCenterOfPressure(self): + # Center of pressure position relative to CDM (center of dry mass) + cpz = self.distanceToCM + np.sign(self.distanceToCM) * (0.288 * self.rootChord) self.cpx = 0 self.cpy = 0 self.cpz = cpz self.cp = (self.cpx, self.cpy, self.cpz) - self.cl = cl - self.rollParameters = rollParameters - self.clalphaMultipleFins = clalphaMultipleFins - self.clalphaSingleFin = clalphaSingleFin - - return None + return self class Tail: From 7e4d18084e8a092844fe9be7ee5ee1f34576b0b3 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 05:40:58 -0300 Subject: [PATCH 06/17] enh: add draw() methods --- rocketpy/AeroSurfaces.py | 148 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index bbb29c896..256972d0c 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -339,6 +339,96 @@ def evaluateCenterOfPressure(self): self.cp = (self.cpx, self.cpy, self.cpz) return self.cp + def draw(self): + # Color cycle [#348ABD, #A60628, #7A68A6, #467821, #D55E00, #CC79A7, #56B4E9, #009E73, #F0E442, #0072B2] + # Fin + leadingEdge = plt.Line2D((0, self.sweepLength), (0, self.span), color="#A60628") + tip = plt.Line2D( + (self.sweepLength, self.sweepLength + self.tipChord), + (self.span, self.span), + color="#A60628", + ) + backEdge = plt.Line2D( + (self.sweepLength + self.tipChord, self.rootChord), + (self.span, 0), + color="#A60628", + ) + root = plt.Line2D((self.rootChord, 0), (0, 0), color="#A60628") + + # Center and Quarter line + center_line = plt.Line2D( + (self.rootChord / 2, self.sweepLength + self.tipChord / 2), + (0, self.span), + color="#7A68A6", + alpha=0.35, + linestyle="--", + label="Center Line", + ) + quarter_line = plt.Line2D( + (self.rootChord / 4, self.sweepLength + self.tipChord / 4), + (0, self.span), + color="#7A68A6", + alpha=1, + linestyle="--", + label="Quarter Line", + ) + + # Center of pressure + cp_point = [abs(self.distanceToCM - self.cpz), self.Yma] + + # Mean Aerodynamic Chord + Yma_start = ( + self.sweepLength + * (self.rootChord + 2 * self.tipChord) + / (3 * (self.rootChord + self.tipChord)) + ) + Yma_end = ( + 2 * self.rootChord**2 + + self.rootChord * self.sweepLength + + 2 * self.rootChord * self.tipChord + + 2 * self.sweepLength * self.tipChord + + 2 * self.tipChord**2 + ) / (3 * (self.rootChord + self.tipChord)) + Yma_line = plt.Line2D( + (Yma_start, Yma_end), + (self.Yma, self.Yma), + color="#467821", + linestyle="--", + label="Mean Aerodynamic Chord", + ) + + # Plotting + fig3 = plt.figure(figsize=(4, 4)) + with plt.style.context("bmh"): + ax1 = fig3.add_subplot(111) + + # Fin + ax1.add_line(leadingEdge) + ax1.add_line(tip) + ax1.add_line(backEdge) + ax1.add_line(root) + + ax1.add_line(center_line) + ax1.add_line(quarter_line) + ax1.add_line(Yma_line) + ax1.scatter( + *cp_point, label="Center Of Pressure", color="red", s=100, zorder=10 + ) + ax1.scatter(*cp_point, facecolors="none", edgecolors="red", s=500, zorder=10) + + # Plot settings + xlim = ( + self.rootChord + if self.sweepLength + self.tipChord < self.rootChord + else self.sweepLength + self.tipChord + ) + ax1.set_xlim(0, xlim * 1.1) + ax1.set_ylim(0, self.span * 1.1) + ax1.set_xlabel("Root Chord") + ax1.set_ylabel("Span") + ax1.set_title("Trapezoidal Fin") + ax1.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left") + class EllipticalFins(Fins): def __init__( @@ -447,6 +537,64 @@ def evaluateCenterOfPressure(self): self.cp = (self.cpx, self.cpy, self.cpz) return self + def draw(self): + # Color cycle [#348ABD, #A60628, #7A68A6, #467821, #D55E00, #CC79A7, #56B4E9, #009E73, #F0E442, #0072B2] + # Ellipse + el = Ellipse( + (self.rootChord / 2, 0), + self.rootChord, + self.span * 2, + fill=False, + edgecolor="#A60628", + linewidth=2, + ) + + # Mean Aerodynamic Chord + Yma_length = 8 * self.rootChord / (3 * np.pi) # From barrowman + Yma_start = (self.rootChord - Yma_length) / 2 + Yma_end = self.rootChord - (self.rootChord - Yma_length) / 2 + Yma_line = plt.Line2D( + (Yma_start, Yma_end), + (self.Yma, self.Yma), + label="Mean Aerodynamic Chord", + color="#467821", + ) + + # Center Line + center_line = plt.Line2D( + (self.rootChord / 2, self.rootChord / 2), + (0, self.span), + color="#7A68A6", + alpha=0.35, + linestyle="--", + label="Center Line", + ) + + # Center of pressure + cp_point = [abs(self.distanceToCM - self.cpz), self.Yma] + + # Plotting + fig3 = plt.figure(figsize=(4, 4)) + with plt.style.context("bmh"): + ax1 = fig3.add_subplot(111) + ax1.add_patch(el) + ax1.add_line(Yma_line) + ax1.add_line(center_line) + ax1.scatter( + *cp_point, label="Center Of Pressure", color="red", s=100, zorder=10 + ) + ax1.scatter(*cp_point, facecolors="none", edgecolors="red", s=500, zorder=10) + + # Plot settings + ax1.set_xlim(0, self.rootChord) + ax1.set_ylim(0, self.span * 1.1) + ax1.set_xlabel("Root Chord") + ax1.set_ylabel("Span") + ax1.set_title("Elliptical Fin") + ax1.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left") + + return self + class Tail: """Class that defines a tail for the rocket. From 3e18215774c6e2957d0c4dc822e4ef22e21e3d82 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 05:41:25 -0300 Subject: [PATCH 07/17] enh: add allInfo() method --- rocketpy/AeroSurfaces.py | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 256972d0c..718a547f7 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -217,6 +217,70 @@ def _finNumCorrection(_, n): else: return n / 2 + def geometricalInfo(self): + print("\n\n Geometrical Parameters\n") + if isinstance(self, TrapezoidalFins): + print("Fin Type: Trapezoidal") + print("Tip Chord: {:.3f} m".format(self.tipChord)) + else: + print("Fin Type: Elliptical") + + print("Root Chord: {:.3f} m".format(self.rootChord)) + print("Span: {:.3f} m".format(self.span)) + print("Cant Angle: {:.3f} °".format(self.cantAngle)) + print("Fin Area: {:.3f} m".format(self.Af)) + print("Aspect Ratio: {:.3f} m".format(self.AR)) + print("Gamma_c: {:.3f} m".format(self.gamma_c)) + print("Mean Aerodynamic Chord: {:.3f} m".format(self.Yma)) + print( + "Roll Geometrical Constant: {:.3f} m".format(self.rollGeometricalConstant) + ) + + self.draw() + + def liftInfo(self): + print("\n\nLift Information\n") + print("Lift Interference Factor: {:.3f} m".format(self.liftInterferenceFactor)) + print( + "Center of Pressure position: ({:.3f},{:.3f},{:.3f}) (x, y, z)".format( + self.cpx, self.cpy, self.cpz + ) + ) + self.clalphaSingleFin() + self.clalphaMultipleFins() + self.cl() + + def rollInfo(self): + print("\n\nRoll Information\n") + print("Cant Angle: {:.3f} °".format(self.cantAngle)) + print("Cant Angle Radians: {:.3f} rad".format(self.cantAngleRad)) + print( + "Roll Damping Interference Factor: {:.3f} rad".format( + self.rollDampingInterferenceFactor + ) + ) + print( + "Roll Forcing Interference Factor: {:.3f} rad".format( + self.rollForcingInterferenceFactor + ) + ) + self.rollParameters[0]() + self.rollParameters[1]() + + def allInfo(self): + print("Fin information\n\n") + + print("Basic Information\n") + + print("Number of Fins: {:.0f}".format(self.n)) + print("Rocket radius at self's position: {:.3f} m".format(self.radius)) + print("Fin Distance to CM: {:.3f} m".format(self.distanceToCM)) + + self.geometricalInfo() + self.liftInfo() + if self.cantAngle: + self.rollInfo() + class TrapezoidalFins(Fins): def __init__( From 21407f309f5662f4cb1da36a42cfc41784e30fdd Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 09:11:39 -0300 Subject: [PATCH 08/17] enh: docs --- rocketpy/AeroSurfaces.py | 440 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 435 insertions(+), 5 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 718a547f7..53aff9ce6 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -88,7 +88,85 @@ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): class Fins: + """Super class that holds common methods for the fin classes. + Should not be instantiated. + + Attributes + ---------- + + Geometrical attributes: + Fins.n : int + Number of fins in fin set + Fins.radius : float + Radius of the rocket at the position of the fin set + Fins.airfoil : tuple + Tuple of two items. First is the airfoil lift curve. + Second is the unit of the curve (radians or degrees) + Fins.distanceToCM : float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. + Fins.cantAngle : float + Fins cant angle with respect to the rocket centerline, in degrees + Fins.cantAngleList : list + List of cant angles that fins are set to throughout a flight + simulation. Useful for control systems + Fins.cantAngleRad : float + Fins cant angle with respect to the rocket centerline, in radians + Fins.rootChord : float + Fin root chord in meters. + Fins.tipChord : float + Fin tip chord in meters. + Fins.span : float + Fin span in meters. + Fins.name : string + Name of fin set + Fins.sweepLength : float + Fins sweep length in meters. By sweep length, understand the axial distance + between the fin root leading edge and the fin tip leading edge measured + parallel to the rocket centerline. + Fins.sweepAngle : float + Fins sweep angle with respect to the rocket centerline. Must + be given in degrees. + Fins.d : float + Diameter of the rocket at the position of the fin set + Fins.Aref : float + Reference area of the rocket + Fins.Af : float + Area of the longtudinal section of each fin in the set + Fins.AR : float + Aspect ratio of each fin in the set + Fins.gamma_c : float + Fin midchord sweep angle + Fins.Yma : float + Span wise position of the mean aerodynamic chord + Fins.rollGeometricalConstant : float + Geometrical constant used in roll calculations + Fins.tau : float + Geometrical relation used to simplify lift and roll calculations + Fins.liftInterferenceFactor : float + Factor of Fin-Body interference in the lift coefficient + """ + def evaluateLiftCoefficient(self): + """Calculates and returns the finset's lift coefficient. + The lift coefficient is saved and returned. This function + also calculates and saves the lift coefficient derivative + for a single fin and the lift coefficient derivative for + a number of n fins corrected for Fin-Body interference. + + Parameters + ---------- + None + + Returns + ------- + self.cl : Function + Function of the angle of attack (Alpha) and the mach number + (Mach) expressing the finset's lift coefficient. The inputs + are the angle of attack (in radians) and the mach number. + The output is the finset's lift coefficient. + """ if not self.airfoil: # Defines clalpha2D as 2*pi for planar fins clalpha2D = Function(lambda mach: 2 * np.pi / self._beta(mach)) @@ -138,9 +216,23 @@ def evaluateLiftCoefficient(self): "Cl", ) - return self + return self.cl def evaluateRollCoefficients(self): + """Calculates and returns the finset's roll coefficients. + The roll coefficients are saved in a list. + + Parameters + ---------- + None + + Returns + ------- + self.rollParameters : list + List containing the roll moment lift coefficient, the + roll moment damping coefficient and the cant angle in + radians + """ clfDelta = ( self.rollForcingInterferenceFactor * self.n @@ -158,9 +250,9 @@ def evaluateRollCoefficients(self): / (self.Aref * self.d**2) ) # Function of mach number self.rollParameters = [clfDelta, cldOmega, self.cantAngleRad] - return self + return self.rollParameters - def changeCantAngle(self, cantAngle): + def changeCantAngle(self, cantAngle): # TODO: make it generic def ChangeParam self.cantAngleList.append(cantAngle) self.cantAngle = cantAngle @@ -172,7 +264,7 @@ def changeCantAngle(self, cantAngle): # Defines beta parameter def _beta(_, mach): - """Defines a parameter that is commonly used in aerodynamic + """Defines a parameter that is often used in aerodynamic equations. It is commonly used in the Prandtl factor which corrects subsonic force coefficients for compressible flow. @@ -218,6 +310,17 @@ def _finNumCorrection(_, n): return n / 2 def geometricalInfo(self): + """Prints out information about geometrical parameters + of the fin set. + + Parameters + ---------- + None + + Return + ------ + None + """ print("\n\n Geometrical Parameters\n") if isinstance(self, TrapezoidalFins): print("Fin Type: Trapezoidal") @@ -238,7 +341,20 @@ def geometricalInfo(self): self.draw() + return None + def liftInfo(self): + """Prints out information about lift parameters + of the fin set. + + Parameters + ---------- + None + + Return + ------ + None + """ print("\n\nLift Information\n") print("Lift Interference Factor: {:.3f} m".format(self.liftInterferenceFactor)) print( @@ -250,7 +366,20 @@ def liftInfo(self): self.clalphaMultipleFins() self.cl() + return None + def rollInfo(self): + """Prints out information about roll parameters + of the fin set. + + Parameters + ---------- + None + + Return + ------ + None + """ print("\n\nRoll Information\n") print("Cant Angle: {:.3f} °".format(self.cantAngle)) print("Cant Angle Radians: {:.3f} rad".format(self.cantAngleRad)) @@ -264,10 +393,24 @@ def rollInfo(self): self.rollForcingInterferenceFactor ) ) + self.rollParameters[0]() self.rollParameters[1]() + return None + def allInfo(self): + """Prints out all data and graphs available about the Rocket. + + Parameters + ---------- + None + + Return + ------ + None + """ + print("Fin information\n\n") print("Basic Information\n") @@ -281,8 +424,69 @@ def allInfo(self): if self.cantAngle: self.rollInfo() + return None + class TrapezoidalFins(Fins): + """Class that defines and holds information for a trapezoidal fin set. + + Attributes + ---------- + + Geometrical attributes: + TrapezoidalFins.n : int + Number of fins in fin set + TrapezoidalFins.radius : float + Radius of the rocket at the position of the fin set + TrapezoidalFins.airfoil : tuple + Tuple of two items. First is the airfoil lift curve. + Second is the unit of the curve (radians or degrees) + TrapezoidalFins.distanceToCM : float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. + TrapezoidalFins.cantAngle : float + Fins cant angle with respect to the rocket centerline, in degrees + TrapezoidalFins.cantAngleList : list + List of cant angles that fins are set to throughout a flight + simulation. Useful for control systems + TrapezoidalFins.cantAngleRad : float + Fins cant angle with respect to the rocket centerline, in radians + TrapezoidalFins.rootChord : float + Fin root chord in meters. + TrapezoidalFins.tipChord : float + Fin tip chord in meters. + TrapezoidalFins.span : float + Fin span in meters. + TrapezoidalFins.name : string + Name of fin set + TrapezoidalFins.sweepLength : float + Fins sweep length in meters. By sweep length, understand the axial distance + between the fin root leading edge and the fin tip leading edge measured + parallel to the rocket centerline. + TrapezoidalFins.sweepAngle : float + Fins sweep angle with respect to the rocket centerline. Must + be given in degrees. + TrapezoidalFins.d : float + Diameter of the rocket at the position of the fin set + TrapezoidalFins.Aref : float + Reference area of the rocket + TrapezoidalFins.Af : float + Area of the longtudinal section of each fin in the set + TrapezoidalFins.AR : float + Aspect ratio of each fin in the set + TrapezoidalFins.gamma_c : float + Fin midchord sweep angle + TrapezoidalFins.Yma : float + Span wise position of the mean aerodynamic chord + TrapezoidalFins.rollGeometricalConstant : float + Geometrical constant used in roll calculations + TrapezoidalFins.tau : float + Geometrical relation used to simplify lift and roll calculations + TrapezoidalFins.liftInterferenceFactor : float + Factor of Fin-Body interference in the lift coefficient + """ + def __init__( self, n, @@ -297,6 +501,65 @@ def __init__( airfoil=None, name="Fins", ): + """Initialize TrapezoidalFins class. + + Parameters + ---------- + n : int + Number of fins, from 2 to infinity. + rootChord : int, float + Fin root chord in meters. + tipChord : int, float + Fin tip chord in meters. + span : int, float + Fin span in meters. + distanceToCM : int, float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. Consider the center point belonging to the top + of the fins to calculate distance. + radius : int, float + Reference radius to calculate lift coefficient. If None, which + is default, use rocket radius. Otherwise, enter the radius + of the rocket in the section of the fins, as this impacts + its lift coefficient. + cantAngle : int, float, optional + Fins cant angle with respect to the rocket centerline. Must + be given in degrees. + sweepLength : int, float, optional + Fins sweep length in meters. By sweep length, understand the axial distance + between the fin root leading edge and the fin tip leading edge measured + parallel to the rocket centerline. If not given, the sweep length is + assumed to be equal the root chord minus the tip chord, in which case the + fin is a right trapezoid with its base perpendicular to the rocket's axis. + Cannot be used in conjunction with sweepAngle. + sweepAngle : int, float, optional + Fins sweep angle with respect to the rocket centerline. Must + be given in degrees. If not given, the sweep angle is automatically + calculated, in which case the fin is assumed to be a right trapezoid with + its base perpendicular to the rocket's axis. + Cannot be used in conjunction with sweepLength. + airfoil : tuple, optional + Default is null, in which case fins will be treated as flat plates. + Otherwise, if tuple, fins will be considered as airfoils. The + tuple's first item specifies the airfoil's lift coefficient + by angle of attack and must be either a .csv, .txt, ndarray + or callable. The .csv and .txt files must contain no headers + and the first column must specify the angle of attack, while + the second column must specify the lift coefficient. The + ndarray should be as [(x0, y0), (x1, y1), (x2, y2), ...] + where x0 is the angle of attack and y0 is the lift coefficient. + If callable, it should take an angle of attack as input and + return the lift coefficient at that angle of attack. + The tuple's second item is the unit of the angle of attack, + accepting either "radians" or "degrees". + name : str + Name of fin set. + + Returns + ------- + None + """ # Check if sweep angle or sweep length is given if sweepLength is not None and sweepAngle is not None: @@ -386,6 +649,19 @@ def __init__( self.evaluateRollCoefficients() def evaluateCenterOfPressure(self): + """Calculates and returns the finset's center of pressure + in relation to the rocket's center of dry mass. the center + of pressure position is saved and stored in a tuple. + + Parameters + ---------- + None + + Returns + ------- + self.cp : tuple + Tuple containing cpx, cpy, cpz. + """ # Center of pressure position relative to CDM (center of dry mass) cpz = self.distanceToCM + np.sign(self.distanceToCM) * ( (self.sweepLength / 3) @@ -404,6 +680,18 @@ def evaluateCenterOfPressure(self): return self.cp def draw(self): + """Draw the fin shape along with some important + information. These being, the center line, the + quarter line and the center of pressure position. + + Parameters + ---------- + None + + Returns + ------- + None + """ # Color cycle [#348ABD, #A60628, #7A68A6, #467821, #D55E00, #CC79A7, #56B4E9, #009E73, #F0E442, #0072B2] # Fin leadingEdge = plt.Line2D((0, self.sweepLength), (0, self.span), color="#A60628") @@ -493,8 +781,67 @@ def draw(self): ax1.set_title("Trapezoidal Fin") ax1.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left") + return None + class EllipticalFins(Fins): + """Class that defines and holds information for an elliptical fin set. + + Attributes + ---------- + + Geometrical attributes: + EllipticalFins.n : int + Number of fins in fin set + EllipticalFins.radius : float + Radius of the rocket at the position of the fin set + EllipticalFins.airfoil : tuple + Tuple of two items. First is the airfoil lift curve. + Second is the unit of the curve (radians or degrees) + EllipticalFins.distanceToCM : float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. + EllipticalFins.cantAngle : float + Fins cant angle with respect to the rocket centerline, in degrees + EllipticalFins.cantAngleList : list + List of cant angles that fins are set to throughout a flight + simulation. Useful for control systems + EllipticalFins.cantAngleRad : float + Fins cant angle with respect to the rocket centerline, in radians + EllipticalFins.rootChord : float + Fin root chord in meters. + EllipticalFins.span : float + Fin span in meters. + EllipticalFins.name : string + Name of fin set + EllipticalFins.sweepLength : float + Fins sweep length in meters. By sweep length, understand the axial distance + between the fin root leading edge and the fin tip leading edge measured + parallel to the rocket centerline. + EllipticalFins.sweepAngle : float + Fins sweep angle with respect to the rocket centerline. Must + be given in degrees. + EllipticalFins.d : float + Diameter of the rocket at the position of the fin set + EllipticalFins.Aref : float + Reference area of the rocket + EllipticalFins.Af : float + Area of the longtudinal section of each fin in the set + EllipticalFins.AR : float + Aspect ratio of each fin in the set + EllipticalFins.gamma_c : float + Fin midchord sweep angle + EllipticalFins.Yma : float + Span wise position of the mean aerodynamic chord + EllipticalFins.rollGeometricalConstant : float + Geometrical constant used in roll calculations + EllipticalFins.tau : float + Geometrical relation used to simplify lift and roll calculations + EllipticalFins.liftInterferenceFactor : float + Factor of Fin-Body interference in the lift coefficient + """ + def __init__( self, n, @@ -506,6 +853,63 @@ def __init__( airfoil=None, name="Fins", ): + """Initialize EllipticalFins class. + + Parameters + ---------- + n : int + Number of fins, from 2 to infinity. + rootChord : int, float + Fin root chord in meters. + span : int, float + Fin span in meters. + distanceToCM : int, float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. Consider the center point belonging to the top + of the fins to calculate distance. + radius : int, float + Reference radius to calculate lift coefficient. If None, which + is default, use rocket radius. Otherwise, enter the radius + of the rocket in the section of the fins, as this impacts + its lift coefficient. + cantAngle : int, float, optional + Fins cant angle with respect to the rocket centerline. Must + be given in degrees. + sweepLength : int, float, optional + Fins sweep length in meters. By sweep length, understand the axial distance + between the fin root leading edge and the fin tip leading edge measured + parallel to the rocket centerline. If not given, the sweep length is + assumed to be equal the root chord minus the tip chord, in which case the + fin is a right trapezoid with its base perpendicular to the rocket's axis. + Cannot be used in conjunction with sweepAngle. + sweepAngle : int, float, optional + Fins sweep angle with respect to the rocket centerline. Must + be given in degrees. If not given, the sweep angle is automatically + calculated, in which case the fin is assumed to be a right trapezoid with + its base perpendicular to the rocket's axis. + Cannot be used in conjunction with sweepLength. + airfoil : tuple, optional + Default is null, in which case fins will be treated as flat plates. + Otherwise, if tuple, fins will be considered as airfoils. The + tuple's first item specifies the airfoil's lift coefficient + by angle of attack and must be either a .csv, .txt, ndarray + or callable. The .csv and .txt files must contain no headers + and the first column must specify the angle of attack, while + the second column must specify the lift coefficient. The + ndarray should be as [(x0, y0), (x1, y1), (x2, y2), ...] + where x0 is the angle of attack and y0 is the lift coefficient. + If callable, it should take an angle of attack as input and + return the lift coefficient at that angle of attack. + The tuple's second item is the unit of the angle of attack, + accepting either "radians" or "degrees". + name : str + Name of fin set. + + Returns + ------- + None + """ # Compute auxiliary geometrical parameters d = 2 * radius @@ -592,7 +996,22 @@ def __init__( ) self.evaluateRollCoefficients() + return None + def evaluateCenterOfPressure(self): + """Calculates and returns the finset's center of pressure + in relation to the rocket's center of dry mass. the center + of pressure position is saved and stored in a tuple. + + Parameters + ---------- + None + + Returns + ------- + self.cp : tuple + Tuple containing cpx, cpy, cpz. + """ # Center of pressure position relative to CDM (center of dry mass) cpz = self.distanceToCM + np.sign(self.distanceToCM) * (0.288 * self.rootChord) self.cpx = 0 @@ -602,6 +1021,17 @@ def evaluateCenterOfPressure(self): return self def draw(self): + """Draw the fin shape along with some important information. + These being, the center line and the center of pressure position. + + Parameters + ---------- + None + + Returns + ------- + None + """ # Color cycle [#348ABD, #A60628, #7A68A6, #467821, #D55E00, #CC79A7, #56B4E9, #009E73, #F0E442, #0072B2] # Ellipse el = Ellipse( @@ -657,7 +1087,7 @@ def draw(self): ax1.set_title("Elliptical Fin") ax1.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left") - return self + return None class Tail: From fdc50855ed7132c3e07910fb9405c897c340c986 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 09:25:32 -0300 Subject: [PATCH 09/17] enh: add setAttribute --- rocketpy/AeroSurfaces.py | 53 ++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 53aff9ce6..b35543995 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -108,9 +108,9 @@ class Fins: nose cone. Fins.cantAngle : float Fins cant angle with respect to the rocket centerline, in degrees - Fins.cantAngleList : list - List of cant angles that fins are set to throughout a flight - simulation. Useful for control systems + Fins.changingAttributeDict : dict + Dictionary that stores the name and the values of the attributes that may + be changed during a simulation. Useful for control systems. Fins.cantAngleRad : float Fins cant angle with respect to the rocket centerline, in radians Fins.rootChord : float @@ -252,15 +252,36 @@ def evaluateRollCoefficients(self): self.rollParameters = [clfDelta, cldOmega, self.cantAngleRad] return self.rollParameters - def changeCantAngle(self, cantAngle): # TODO: make it generic def ChangeParam - self.cantAngleList.append(cantAngle) + def setAttribute(self, name, value): + """Changes an existing attribute to a new value and + reruns the evaluate methods. - self.cantAngle = cantAngle - self.cantAngleRad = np.radians(cantAngle) + Parameters + ---------- + name : strig + Name of the attribute that will be changed. + value : any + value to which the attribute will be changed to. + Returns + ------- + None + """ + # Changes attribute value + self.__dict__[name] = value + + # Add changed attribute to dict + if self.changingAttributeDict.has_key(name): + self.changingAttributeDict[name].append(value) + else: + self.changingAttributeDict[name] = [value] + + # Rerun important calculations + self.evaluateCenterOfPressure() + self.evaluateLiftCoefficient() self.evaluateRollCoefficients() - return self + return None # Defines beta parameter def _beta(_, mach): @@ -447,9 +468,9 @@ class TrapezoidalFins(Fins): nose cone. TrapezoidalFins.cantAngle : float Fins cant angle with respect to the rocket centerline, in degrees - TrapezoidalFins.cantAngleList : list - List of cant angles that fins are set to throughout a flight - simulation. Useful for control systems + TrapezoidalFins.changingAttributeDict : dict + Dictionary that stores the name and the values of the attributes that may + be changed during a simulation. Useful for control systems. TrapezoidalFins.cantAngleRad : float Fins cant angle with respect to the rocket centerline, in radians TrapezoidalFins.rootChord : float @@ -601,7 +622,7 @@ def __init__( self.airfoil = airfoil self.distanceToCM = distanceToCM self.cantAngle = cantAngle - self.cantAngleList = [cantAngle] + self.changingAttributeDict = {} self.cantAngleRad = np.radians(cantAngle) self.rootChord = rootChord self.tipChord = tipChord @@ -804,9 +825,9 @@ class EllipticalFins(Fins): nose cone. EllipticalFins.cantAngle : float Fins cant angle with respect to the rocket centerline, in degrees - EllipticalFins.cantAngleList : list - List of cant angles that fins are set to throughout a flight - simulation. Useful for control systems + EllipticalFins.changingAttributeDict : dict + Dictionary that stores the name and the values of the attributes that may + be changed during a simulation. Useful for control systems. EllipticalFins.cantAngleRad : float Fins cant angle with respect to the rocket centerline, in radians EllipticalFins.rootChord : float @@ -937,7 +958,7 @@ def __init__( self.airfoil = airfoil self.distanceToCM = distanceToCM self.cantAngle = cantAngle - self.cantAngleList = [cantAngle] + self.changingAttributeDict = {} self.cantAngleRad = np.radians(cantAngle) self.rootChord = rootChord self.span = span From 024ea0f8c57b1ef7258260a2e7671442f4d80b8f Mon Sep 17 00:00:00 2001 From: MateusStano Date: Tue, 1 Nov 2022 10:04:13 -0300 Subject: [PATCH 10/17] bug: fixed setAttribute and fin area calculation --- rocketpy/AeroSurfaces.py | 144 ++++++++++++++++++++------------------- rocketpy/Rocket.py | 4 +- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index b35543995..6bfa8ab0e 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -233,6 +233,9 @@ def evaluateRollCoefficients(self): roll moment damping coefficient and the cant angle in radians """ + + self.cantAngleRad = np.radians(self.cantAngle) + clfDelta = ( self.rollForcingInterferenceFactor * self.n @@ -271,7 +274,7 @@ def setAttribute(self, name, value): self.__dict__[name] = value # Add changed attribute to dict - if self.changingAttributeDict.has_key(name): + if self.changingAttributeDict.get(name) != None: self.changingAttributeDict[name].append(value) else: self.changingAttributeDict[name] = [value] @@ -442,8 +445,7 @@ def allInfo(self): self.geometricalInfo() self.liftInfo() - if self.cantAngle: - self.rollInfo() + self.rollInfo() return None @@ -615,6 +617,27 @@ def __init__( # Fin–body interference correction parameters tau = (span + radius) / radius liftInterferenceFactor = 1 + 1 / tau + # Parameters for Roll Moment. + # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf + λ = tipChord / rootChord + rollDampingInterferenceFactor = 1 + ( + ((tau - λ) / (tau)) - ((1 - λ) / (tau - 1)) * np.log(tau) + ) / ( + ((tau + 1) * (tau - λ)) / (2) - ((1 - λ) * (tau**3 - 1)) / (3 * (tau - 1)) + ) + rollForcingInterferenceFactor = (1 / np.pi**2) * ( + (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) + + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) + + ((tau**2 + 1) ** 2) + / (tau**2 * (tau - 1) ** 2) + * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 + - (4 * (tau + 1)) + / (tau * (tau - 1)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) + ) # Store values self.n = n @@ -623,7 +646,6 @@ def __init__( self.distanceToCM = distanceToCM self.cantAngle = cantAngle self.changingAttributeDict = {} - self.cantAngleRad = np.radians(cantAngle) self.rootChord = rootChord self.tipChord = tipChord self.span = span @@ -633,41 +655,21 @@ def __init__( self.d = d self.Aref = Aref # Reference area self.Yr = Yr - self.Af = Af * span / 2 # Fin area + self.Af = Af # Fin area self.AR = AR # Fin aspect ratio self.gamma_c = gamma_c # Mid chord angle self.Yma = Yma # Span wise coord of mean aero chord self.rollGeometricalConstant = rollGeometricalConstant self.tau = tau self.liftInterferenceFactor = liftInterferenceFactor + self.λ = λ + self.rollDampingInterferenceFactor = rollDampingInterferenceFactor + self.rollForcingInterferenceFactor = rollForcingInterferenceFactor self.evaluateCenterOfPressure() self.evaluateLiftCoefficient() - if cantAngle: - # Parameters for Roll Moment. - # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf - self.λ = tipChord / rootChord - self.rollDampingInterferenceFactor = 1 + ( - ((tau - self.λ) / (tau)) - ((1 - self.λ) / (tau - 1)) * np.log(tau) - ) / ( - ((tau + 1) * (tau - self.λ)) / (2) - - ((1 - self.λ) * (tau**3 - 1)) / (3 * (tau - 1)) - ) - self.rollForcingInterferenceFactor = (1 / np.pi**2) * ( - (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) - + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) - + ((tau**2 + 1) ** 2) - / (tau**2 * (tau - 1) ** 2) - * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 - - (4 * (tau + 1)) - / (tau * (tau - 1)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) - ) - self.evaluateRollCoefficients() + self.evaluateRollCoefficients() def evaluateCenterOfPressure(self): """Calculates and returns the finset's center of pressure @@ -951,6 +953,44 @@ def __init__( # Fin–body interference correction parameters tau = (span + radius) / radius liftInterferenceFactor = 1 + 1 / tau + rollDampingInterferenceFactor = 1 + ( + (radius**2) + * ( + 2 + * (radius**2) + * np.sqrt(span**2 - radius**2) + * np.log( + (2 * span * np.sqrt(span**2 - radius**2) + 2 * span**2) + / radius + ) + - 2 + * (radius**2) + * np.sqrt(span**2 - radius**2) + * np.log(2 * span) + + 2 * span**3 + - np.pi * radius * span**2 + - 2 * (radius**2) * span + + np.pi * radius**3 + ) + ) / ( + 2 + * (span**2) + * (span / 3 + np.pi * radius / 4) + * (span**2 - radius**2) + ) + rollForcingInterferenceFactor = (1 / np.pi**2) * ( + (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) + + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) + + ((tau**2 + 1) ** 2) + / (tau**2 * (tau - 1) ** 2) + * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 + - (4 * (tau + 1)) + / (tau * (tau - 1)) + * np.arcsin((tau**2 - 1) / (tau**2 + 1)) + + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) + ) # Store values self.n = n @@ -965,57 +1005,19 @@ def __init__( self.name = name self.d = d self.Aref = Aref # Reference area - self.Af = Af * span / 2 # Fin area + self.Af = Af # Fin area self.AR = AR # Fin aspect ratio self.gamma_c = gamma_c # Mid chord angle self.Yma = Yma # Span wise coord of mean aero chord self.rollGeometricalConstant = rollGeometricalConstant self.tau = tau self.liftInterferenceFactor = liftInterferenceFactor + self.rollDampingInterferenceFactor = rollDampingInterferenceFactor + self.rollForcingInterferenceFactor = rollForcingInterferenceFactor self.evaluateCenterOfPressure() self.evaluateLiftCoefficient() - - if cantAngle: - self.rollDampingInterferenceFactor = 1 + ( - (radius**2) - * ( - 2 - * (radius**2) - * np.sqrt(span**2 - radius**2) - * np.log( - (2 * span * np.sqrt(span**2 - radius**2) + 2 * span**2) - / radius - ) - - 2 - * (radius**2) - * np.sqrt(span**2 - radius**2) - * np.log(2 * span) - + 2 * span**3 - - np.pi * radius * span**2 - - 2 * (radius**2) * span - + np.pi * radius**3 - ) - ) / ( - 2 - * (span**2) - * (span / 3 + np.pi * radius / 4) - * (span**2 - radius**2) - ) - self.rollForcingInterferenceFactor = (1 / np.pi**2) * ( - (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) - + ((np.pi * (tau**2 + 1) ** 2) / (tau**2 * (tau - 1) ** 2)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - - (2 * np.pi * (tau + 1)) / (tau * (tau - 1)) - + ((tau**2 + 1) ** 2) - / (tau**2 * (tau - 1) ** 2) - * (np.arcsin((tau**2 - 1) / (tau**2 + 1))) ** 2 - - (4 * (tau + 1)) - / (tau * (tau - 1)) - * np.arcsin((tau**2 - 1) / (tau**2 + 1)) - + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) - ) - self.evaluateRollCoefficients() + self.evaluateRollCoefficients() return None diff --git a/rocketpy/Rocket.py b/rocketpy/Rocket.py index fdd4ea15d..c6dda8666 100644 --- a/rocketpy/Rocket.py +++ b/rocketpy/Rocket.py @@ -535,10 +535,10 @@ def addTrapezoidalFins( tipChord, span, distanceToCM, + radius, cantAngle, sweepLength, sweepAngle, - radius, airfoil, name, ) @@ -621,7 +621,7 @@ def addEllipticalFins( # Create a fin set as an object of EllipticalFins class finSet = EllipticalFins( - n, rootChord, span, distanceToCM, cantAngle, radius, airfoil, name + n, rootChord, span, distanceToCM, radius, cantAngle, airfoil, name ) # Add fin set to the list of aerodynamic surfaces From 57a93e729f0f70b3f9693d9777e483c3bb66d4e4 Mon Sep 17 00:00:00 2001 From: Guilherme Fernandes Alves Date: Tue, 1 Nov 2022 17:06:51 +0100 Subject: [PATCH 11/17] MAINT: removing duplicated cLift definition Co-authored-by: Giovani Hidalgo Ceotto --- rocketpy/Flight.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py index 3d8b61d77..ab3c0a478 100644 --- a/rocketpy/Flight.py +++ b/rocketpy/Flight.py @@ -1413,7 +1413,6 @@ def uDot(self, t, u, postProcessing=False): if -1 * compStreamVzBn < 1: compAttackAngle = np.arccos(-compStreamVzBn) cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) - cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) # Component lift force magnitude compLift = ( 0.5 * rho * (compStreamSpeed**2) * self.rocket.area * cLift From 6b906d5af1f8764b42bf1cd6708fe22b5a453e1b Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Wed, 2 Nov 2022 08:13:49 +0100 Subject: [PATCH 12/17] ENH: updating 2D plots at Function.py --- rocketpy/Function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 8904cb54b..8602436ce 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -1123,7 +1123,7 @@ def plot2D( """ # Prepare plot figure = plt.figure() - axes = figure.gca(projection="3d") + axes = figure.add_subplot(111, projection="3d") # Define a mesh and f values at mesh nodes for plotting if callable(self.source): # Determine boundaries From 01819ac22dfa3e5ae2aaf8f17110c04dff7184e6 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Wed, 2 Nov 2022 08:17:12 +0100 Subject: [PATCH 13/17] MAINT: updating docstrings of AeroSurfaces --- rocketpy/AeroSurfaces.py | 227 ++++++++++++++++++++++++--------------- 1 file changed, 142 insertions(+), 85 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 6bfa8ab0e..4b8153368 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -1,10 +1,11 @@ -__author__ = "Guilherme Fernandes Alves" +__author__ = "Guilherme Fernandes Alves, Mateus Stano Junqueira" __copyright__ = "Copyright 20XX, RocketPy Team" __license__ = "MIT" -import numpy as np import matplotlib.pyplot as plt +import numpy as np from matplotlib.patches import Ellipse + from .Function import Function @@ -27,10 +28,21 @@ class NoseCone: Tuple with the x, y and z coordinates of the nose cone center of pressure relative to the rocket center of mass. Has units of length and must be given in meters. + NoseCone.cpx : float + Nose cone center of pressure x coordinate relative to the rocket center + of mass. Has units of length and must be given in meters. + NoseCone.cpy : float + Nose cone center of pressure y coordinate relative to the rocket center + of mass. Has units of length and must be given in meters. + NoseCone.cpz : float + Nose cone center of pressure z coordinate relative to the rocket center + of mass. Has units of length and must be given in meters. NoseCone.cl : Function Function which defines the lift coefficient as a function of the angle of attack and the Mach number. It must take as input the angle of attack in radians and the Mach number. It should return the lift coefficient. + NoseCone.clalpha : float + Lift coefficient derivative. Has units of 1/rad. """ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): @@ -43,7 +55,7 @@ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): Nose cone length. Has units of length and must be given in meters. kind : string Nose cone kind. Can be "conical", "ogive" or "lvhaack". - distanceToCM : _type_ + distanceToCM : float Distance between nose cone tip and rocket center of mass. Has units of length and must be given in meters. name : str, optional @@ -91,65 +103,71 @@ class Fins: """Super class that holds common methods for the fin classes. Should not be instantiated. - Attributes + Parameters ---------- + None - Geometrical attributes: - Fins.n : int - Number of fins in fin set - Fins.radius : float - Radius of the rocket at the position of the fin set - Fins.airfoil : tuple - Tuple of two items. First is the airfoil lift curve. - Second is the unit of the curve (radians or degrees) - Fins.distanceToCM : float - Fin set position relative to rocket unloaded center of - mass, considering positive direction from center of mass to - nose cone. - Fins.cantAngle : float - Fins cant angle with respect to the rocket centerline, in degrees - Fins.changingAttributeDict : dict - Dictionary that stores the name and the values of the attributes that may - be changed during a simulation. Useful for control systems. - Fins.cantAngleRad : float - Fins cant angle with respect to the rocket centerline, in radians - Fins.rootChord : float - Fin root chord in meters. - Fins.tipChord : float - Fin tip chord in meters. - Fins.span : float - Fin span in meters. - Fins.name : string - Name of fin set - Fins.sweepLength : float - Fins sweep length in meters. By sweep length, understand the axial distance - between the fin root leading edge and the fin tip leading edge measured - parallel to the rocket centerline. - Fins.sweepAngle : float - Fins sweep angle with respect to the rocket centerline. Must - be given in degrees. - Fins.d : float - Diameter of the rocket at the position of the fin set - Fins.Aref : float - Reference area of the rocket - Fins.Af : float - Area of the longtudinal section of each fin in the set - Fins.AR : float - Aspect ratio of each fin in the set - Fins.gamma_c : float - Fin midchord sweep angle - Fins.Yma : float - Span wise position of the mean aerodynamic chord - Fins.rollGeometricalConstant : float - Geometrical constant used in roll calculations - Fins.tau : float - Geometrical relation used to simplify lift and roll calculations - Fins.liftInterferenceFactor : float - Factor of Fin-Body interference in the lift coefficient + Attributes + ---------- + Fins.n : int + Number of fins in fin set + Fins.radius : float + Radius of the rocket at the position of the fin set + Fins.airfoil : tuple + Tuple of two items. First is the airfoil lift curve. + Second is the unit of the curve (radians or degrees) + Fins.distanceToCM : float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. + Fins.cantAngle : float + Fins cant angle with respect to the rocket centerline, in degrees + Fins.cl : Function + Function which defines the lift coefficient as a function of the angle of + attack and the Mach number. It must take as input the angle of attack in + radians and the Mach number. It should return the lift coefficient. + Fins.changingAttributeDict : dict + Dictionary that stores the name and the values of the attributes that may + be changed during a simulation. Useful for control systems. + Fins.cantAngleRad : float + Fins cant angle with respect to the rocket centerline, in radians + Fins.rootChord : float + Fin root chord in meters. + Fins.tipChord : float + Fin tip chord in meters. + Fins.span : float + Fin span in meters. + Fins.name : string + Name of fin set + Fins.sweepLength : float + Fins sweep length in meters. By sweep length, understand the axial distance + between the fin root leading edge and the fin tip leading edge measured + parallel to the rocket centerline. + Fins.sweepAngle : float + Fins sweep angle with respect to the rocket centerline. Must + be given in degrees. + Fins.d : float + Diameter of the rocket at the position of the fin set + Fins.Aref : float + Reference area of the rocket + Fins.Af : float + Area of the longitudinal section of each fin in the set + Fins.AR : float + Aspect ratio of each fin in the set + Fins.gamma_c : float + Fin mid-chord sweep angle + Fins.Yma : float + Span wise position of the mean aerodynamic chord + Fins.rollGeometricalConstant : float + Geometrical constant used in roll calculations + Fins.tau : float + Geometrical relation used to simplify lift and roll calculations + Fins.liftInterferenceFactor : float + Factor of Fin-Body interference in the lift coefficient """ def evaluateLiftCoefficient(self): - """Calculates and returns the finset's lift coefficient. + """Calculates and returns the lift coefficient of the fin set. The lift coefficient is saved and returned. This function also calculates and saves the lift coefficient derivative for a single fin and the lift coefficient derivative for @@ -163,9 +181,9 @@ def evaluateLiftCoefficient(self): ------- self.cl : Function Function of the angle of attack (Alpha) and the mach number - (Mach) expressing the finset's lift coefficient. The inputs + (Mach) expressing the lift coefficient of the fin set. The inputs are the angle of attack (in radians) and the mach number. - The output is the finset's lift coefficient. + The output is the lift coefficient of the fin set. """ if not self.airfoil: # Defines clalpha2D as 2*pi for planar fins @@ -219,7 +237,7 @@ def evaluateLiftCoefficient(self): return self.cl def evaluateRollCoefficients(self): - """Calculates and returns the finset's roll coefficients. + """Calculates and returns the fin set's roll coefficients. The roll coefficients are saved in a list. Parameters @@ -261,7 +279,7 @@ def setAttribute(self, name, value): Parameters ---------- - name : strig + name : string Name of the attribute that will be changed. value : any value to which the attribute will be changed to. @@ -418,8 +436,8 @@ def rollInfo(self): ) ) - self.rollParameters[0]() - self.rollParameters[1]() + self.rollParameters[0]() # TODO: improve this plot + self.rollParameters[1]() # TODO: improve this plot return None @@ -495,11 +513,11 @@ class TrapezoidalFins(Fins): TrapezoidalFins.Aref : float Reference area of the rocket TrapezoidalFins.Af : float - Area of the longtudinal section of each fin in the set + Area of the longitudinal section of each fin in the set TrapezoidalFins.AR : float Aspect ratio of each fin in the set TrapezoidalFins.gamma_c : float - Fin midchord sweep angle + Fin mid-chord sweep angle TrapezoidalFins.Yma : float Span wise position of the mean aerodynamic chord TrapezoidalFins.rollGeometricalConstant : float @@ -672,7 +690,7 @@ def __init__( self.evaluateRollCoefficients() def evaluateCenterOfPressure(self): - """Calculates and returns the finset's center of pressure + """Calculates and returns the center of pressure of the fin set in relation to the rocket's center of dry mass. the center of pressure position is saved and stored in a tuple. @@ -850,11 +868,11 @@ class EllipticalFins(Fins): EllipticalFins.Aref : float Reference area of the rocket EllipticalFins.Af : float - Area of the longtudinal section of each fin in the set + Area of the longitudinal section of each fin in the set EllipticalFins.AR : float Aspect ratio of each fin in the set EllipticalFins.gamma_c : float - Fin midchord sweep angle + Fin mid-chord sweep angle EllipticalFins.Yma : float Span wise position of the mean aerodynamic chord EllipticalFins.rollGeometricalConstant : float @@ -1022,7 +1040,7 @@ def __init__( return None def evaluateCenterOfPressure(self): - """Calculates and returns the finset's center of pressure + """Calculates and returns the center of pressure of the fin set. in relation to the rocket's center of dry mass. the center of pressure position is saved and stored in a tuple. @@ -1067,7 +1085,7 @@ def draw(self): ) # Mean Aerodynamic Chord - Yma_length = 8 * self.rootChord / (3 * np.pi) # From barrowman + Yma_length = 8 * self.rootChord / (3 * np.pi) # From Barrowman's theory Yma_start = (self.rootChord - Yma_length) / 2 Yma_end = self.rootChord - (self.rootChord - Yma_length) / 2 Yma_line = plt.Line2D( @@ -1114,37 +1132,76 @@ def draw(self): class Tail: - """Class that defines a tail for the rocket. + """Class that defines a tail for the rocket. Currently only accepts + conical tails. Parameters ---------- - length : int, float - Length of the tail. - ... + Tail.topRadius : int, float + Radius of the top of the tail. The top radius is defined as the radius + of the transversal section that is closest to the rocket's nose. + Tail.bottomRadius : int, float + Radius of the bottom of the tail. + Tail.length : int, float + Length of the tail. The length is defined as the distance between the + top and bottom of the tail. The length is measured along the rocket's + longitudinal axis. Has the unit of meters. + Tail.distanceToCM : int, float + Distance from the center of dry mass to the center of the tail. + Tail.radius: int, float + The radius of the rocket's body at the tail's position. + Tail.name : str + Name of the tail. Default is 'Tail'. + Attributes + ---------- + Tail.cpx : int, float + x coordinate of the center of pressure of the tail. + Tail.cpy : int, float + y coordinate of the center of pressure of the tail. + Tail.cpz : int, float + z coordinate of the center of pressure of the tail. + Tail.cp : tuple + Tuple containing the coordinates of the center of pressure of the tail. + Tail.cl : Function + Function that returns the lift coefficient of the tail. The function + is defined as a function of the angle of attack and the mach number. + Tail.clalpha : float + Lift coefficient slope. Has the unit of 1/rad. + Tail.slantLength : float + Slant length of the tail. The slant length is defined as the distance + between the top and bottom of the tail. The slant length is measured + along the tail's slant axis. Has the unit of meters. + Tail.surfaceArea : float + Surface area of the tail. Has the unit of meters squared. """ def __init__( self, topRadius, bottomRadius, length, distanceToCM, radius, name="Tail" ): - """_summary_ + """Initializes the tail object by computing and storing the most + important values. Parameters ---------- - topRadius : _type_ - _description_ - bottomRadius : _type_ - _description_ - length : _type_ - _description_ - distanceToCM : _type_ - _description_ + topRadius : int, float + Radius of the top of the tail. The top radius is defined as the radius + of the transversal section that is closest to the rocket's nose. + bottomRadius : int, float + Radius of the bottom of the tail. + length : int, float + Length of the tail. + distanceToCM : int, float + Distance from the center of dry mass to the center of the tail. + radius : int, float + The radius of the rocket's body at the tail's position. + name : str + Name of the tail. Default is 'Tail'. Returns ------- - _type_ - _description_ + None """ # Store arguments as attributes From 8c0fb8abff1ac266fe0190e5b4c137fffa3818b8 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Wed, 2 Nov 2022 08:17:47 +0100 Subject: [PATCH 14/17] ENH: adding final plots do AeroSurfaces --- rocketpy/AeroSurfaces.py | 134 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 8 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 4b8153368..7e0113fcc 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -42,7 +42,7 @@ class NoseCone: attack and the Mach number. It must take as input the angle of attack in radians and the Mach number. It should return the lift coefficient. NoseCone.clalpha : float - Lift coefficient derivative. Has units of 1/rad. + Lift coefficient slope. Has units of 1/rad. """ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): @@ -98,6 +98,61 @@ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): return None + def geometricInfo(self): + """Prints out all the geometric information of the nose cone. + + Parameters + ---------- + None + + Returns + ------- + None + """ + print("Nose Cone Geometric Information of Nose: {}".format(self.name)) + print("-------------------------------") + print("Length: ", self.length) + print("Kind: ", self.kind) + print(f"Distance to rocket's dry CM: {self.distanceToCM:.3f} m") + + return None + + def aerodynamicInfo(self): + """Prints out all the aerodynamic information of the nose cone. + + Parameters + ---------- + None + + Returns + ------- + None + """ + print("Nose Cone Aerodynamic Information of Nose: {}".format(self.name)) + print("-------------------------------") + print("Center of Pressure position: ", self.cp, "m") + print("Lift Coefficient Slope: ", self.clalpha) + print("Lift Coefficient as a function of Alpha and Mach:") + self.cl() + + return None + + def allInfo(self): + """Prints out all the geometric and aerodynamic information of the nose cone. + + Parameters + ---------- + None + + Returns + ------- + None + """ + self.geometricInfo() + self.aerodynamicInfo() + + return None + class Fins: """Super class that holds common methods for the fin classes. @@ -363,7 +418,7 @@ def geometricalInfo(self): ------ None """ - print("\n\n Geometrical Parameters\n") + print("\n Geometrical Parameters\n") if isinstance(self, TrapezoidalFins): print("Fin Type: Trapezoidal") print("Tip Chord: {:.3f} m".format(self.tipChord)) @@ -373,7 +428,7 @@ def geometricalInfo(self): print("Root Chord: {:.3f} m".format(self.rootChord)) print("Span: {:.3f} m".format(self.span)) print("Cant Angle: {:.3f} °".format(self.cantAngle)) - print("Fin Area: {:.3f} m".format(self.Af)) + print("Longitudinal Section Area: {:.3f} m".format(self.Af)) print("Aspect Ratio: {:.3f} m".format(self.AR)) print("Gamma_c: {:.3f} m".format(self.gamma_c)) print("Mean Aerodynamic Chord: {:.3f} m".format(self.Yma)) @@ -397,15 +452,23 @@ def liftInfo(self): ------ None """ - print("\n\nLift Information\n") + print("\nLift Information") + print("----------------") print("Lift Interference Factor: {:.3f} m".format(self.liftInterferenceFactor)) print( "Center of Pressure position: ({:.3f},{:.3f},{:.3f}) (x, y, z)".format( self.cpx, self.cpy, self.cpz ) ) + print( + "Lift Coefficient derivative as a Function of Alpha and Mach for Single Fin" + ) self.clalphaSingleFin() + print( + "Lift Coefficient derivative as a Function of Alpha and Mach for the Fin Set" + ) self.clalphaMultipleFins() + print("Lift Coefficient as a Function of Alpha and Mach for the Fin Set") self.cl() return None @@ -422,9 +485,13 @@ def rollInfo(self): ------ None """ - print("\n\nRoll Information\n") - print("Cant Angle: {:.3f} °".format(self.cantAngle)) - print("Cant Angle Radians: {:.3f} rad".format(self.cantAngleRad)) + print("\nRoll Information") + print("----------------") + print( + "Cant Angle: {:.3f} ° or {:.3f} rad".format( + self.cantAngle, self.cantAngleRad + ) + ) print( "Roll Damping Interference Factor: {:.3f} rad".format( self.rollDampingInterferenceFactor @@ -453,7 +520,7 @@ def allInfo(self): None """ - print("Fin information\n\n") + print("Fin set information\n\n") print("Basic Information\n") @@ -1218,6 +1285,15 @@ def __init__( # Retrieve reference radius rref = self.radius + # Calculate tail slant length + self.slantLength = np.sqrt( + (self.length) ** 2 + (self.topRadius - self.bottomRadius) ** 2 + ) + # Calculate the surface area of the tail + self.surfaceArea = ( + np.pi * self.slantLength * (self.topRadius + self.bottomRadius) + ) + # Calculate cp position relative to center of dry mass if distanceToCM < 0: cpz = distanceToCM - (length / 3) * (1 + (1 - r) / (1 - r**2)) @@ -1241,3 +1317,45 @@ def __init__( self.clalpha = clalpha return None + + def geometricInfo(self): + """Prints out all the geometric information of the tail. + + Returns + ------- + None + """ + + print(f"\nTail name: {self.name}") + print(f"Tail Top Radius: {self.topRadius:.3f} m") + print(f"Tail Bottom Radius: {self.bottomRadius:.3f} m") + print(f"Tail Length: {self.length:.3f} m") + print(f"Tail Distance to Center of Dry Mass: {self.distanceToCM:.3f} m") + print(f"Rocket body radius at tail position: {2*self.radius:.3f} m") + print(f"Tail Slant Length: {self.slantLength:.3f} m") + print(f"Tail Surface Area: {self.surfaceArea:.6f} m^2") + + return None + + def aerodynamicInfo(self): + + print(f"\nTail name: {self.name}") + print(f"Tail Center of Pressure: {self.cp}") + # print(f"Tail Lift Coefficient: {self.cl}") + print(f"Tail Lift Coefficient Slope: {self.clalpha}") + print("Tail Lift Coefficient as a function of Alpha and Mach:") + self.cl() + + return None + + def allInfo(self): + """Prints all the information about the tail object. + + Returns + ------- + None + """ + self.geometricInfo() + self.aerodynamicInfo() + + return None From edc18956999d9917527c09a0ec9bf020d252029f Mon Sep 17 00:00:00 2001 From: MateusStano Date: Thu, 3 Nov 2022 04:33:46 -0300 Subject: [PATCH 15/17] MAINT: changed back to using trapezoidal fins with Calisto --- docs/notebooks/getting_started.ipynb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/getting_started.ipynb b/docs/notebooks/getting_started.ipynb index b88d4e92a..076263fdf 100644 --- a/docs/notebooks/getting_started.ipynb +++ b/docs/notebooks/getting_started.ipynb @@ -337,8 +337,15 @@ "source": [ "NoseCone = Calisto.addNose(length=0.55829, kind=\"vonKarman\", distanceToCM=0.71971)\n", "\n", - "FinSet = Calisto.addEllipticalFins(\n", - " 4, span=0.100, rootChord=0.120, distanceToCM=-1.04956\n", + "FinSet = Calisto.addTrapezoidalFins(\n", + " n=4,\n", + " rootChord=0.120,\n", + " tipChord=0.040,\n", + " span=0.100,\n", + " distanceToCM=-1.04956,\n", + " cantAngle=0,\n", + " radius=None,\n", + " airfoil=None,\n", ")\n", "\n", "Tail = Calisto.addTail(\n", From e721530f48549747c4c2dc84fcff4f54b9127bbb Mon Sep 17 00:00:00 2001 From: MateusStano Date: Thu, 3 Nov 2022 04:34:16 -0300 Subject: [PATCH 16/17] ENH: remaining TODOs --- rocketpy/AeroSurfaces.py | 441 +++++++++++++++++++++++---------------- rocketpy/Flight.py | 13 +- rocketpy/Rocket.py | 2 +- 3 files changed, 267 insertions(+), 189 deletions(-) diff --git a/rocketpy/AeroSurfaces.py b/rocketpy/AeroSurfaces.py index 7e0113fcc..fcd3064a9 100644 --- a/rocketpy/AeroSurfaces.py +++ b/rocketpy/AeroSurfaces.py @@ -7,6 +7,7 @@ from matplotlib.patches import Ellipse from .Function import Function +from abc import ABC, abstractmethod, abstractproperty class NoseCone: @@ -45,7 +46,7 @@ class NoseCone: Lift coefficient slope. Has units of 1/rad. """ - def __init__(self, length, kind, distanceToCM, name="Nose Cone"): + def __init__(self, length, kind, distanceToCM, rocketRadius, name="Nose Cone"): """Initializes the nose cone. It is used to define the nose cone length, kind, distance to center of mass and name. @@ -58,6 +59,8 @@ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): distanceToCM : float Distance between nose cone tip and rocket center of mass. Has units of length and must be given in meters. + rocketRadius : int, float + The radius of the rocket's body at the nose cone position. name : str, optional Nose cone name. Has no impact in simulation, as it is only used to display data in a more organized matter. @@ -70,6 +73,7 @@ def __init__(self, length, kind, distanceToCM, name="Nose Cone"): self.kind = kind self.distanceToCM = distanceToCM self.name = name + self.rocketRadius = rocketRadius # Analyze type if self.kind == "conical": @@ -154,19 +158,15 @@ def allInfo(self): return None -class Fins: - """Super class that holds common methods for the fin classes. - Should not be instantiated. - - Parameters - ---------- - None +class Fins(ABC): + """Abstract class that holds common methods for the fin classes. + Cannot be instantiated. Attributes ---------- Fins.n : int Number of fins in fin set - Fins.radius : float + Fins.rocketRadius : float Radius of the rocket at the position of the fin set Fins.airfoil : tuple Tuple of two items. First is the airfoil lift curve. @@ -177,10 +177,6 @@ class Fins: nose cone. Fins.cantAngle : float Fins cant angle with respect to the rocket centerline, in degrees - Fins.cl : Function - Function which defines the lift coefficient as a function of the angle of - attack and the Mach number. It must take as input the angle of attack in - radians and the Mach number. It should return the lift coefficient. Fins.changingAttributeDict : dict Dictionary that stores the name and the values of the attributes that may be changed during a simulation. Useful for control systems. @@ -204,7 +200,7 @@ class Fins: Fins.d : float Diameter of the rocket at the position of the fin set Fins.Aref : float - Reference area of the rocket + Reference area of the rocket at the fin set position. Fins.Af : float Area of the longitudinal section of each fin in the set Fins.AR : float @@ -221,8 +217,97 @@ class Fins: Factor of Fin-Body interference in the lift coefficient """ + def __init__( + self, + n, + rootChord, + span, + distanceToCM, + rocketRadius, + cantAngle=0, + airfoil=None, + name="Fins", + ): + """Initialize Fins class. + + Parameters + ---------- + n : int + Number of fins, from 2 to infinity. + rootChord : int, float + Fin root chord in meters. + span : int, float + Fin span in meters. + distanceToCM : int, float + Fin set position relative to rocket unloaded center of + mass, considering positive direction from center of mass to + nose cone. Consider the center point belonging to the top + of the fins to calculate distance. + rocketRadius : int, float + Reference radius to calculate lift coefficient. + cantAngle : int, float, optional + Fins cant angle with respect to the rocket centerline. Must + be given in degrees. + airfoil : tuple, optional + Default is null, in which case fins will be treated as flat plates. + Otherwise, if tuple, fins will be considered as airfoils. The + tuple's first item specifies the airfoil's lift coefficient + by angle of attack and must be either a .csv, .txt, ndarray + or callable. The .csv and .txt files must contain no headers + and the first column must specify the angle of attack, while + the second column must specify the lift coefficient. The + ndarray should be as [(x0, y0), (x1, y1), (x2, y2), ...] + where x0 is the angle of attack and y0 is the lift coefficient. + If callable, it should take an angle of attack as input and + return the lift coefficient at that angle of attack. + The tuple's second item is the unit of the angle of attack, + accepting either "radians" or "degrees". + name : str + Name of fin set. + + Returns + ------- + None + """ + + # Compute auxiliary geometrical parameters + d = 2 * rocketRadius + Aref = np.pi * rocketRadius**2 # Reference area + + # Store values + self.n = n + self.rocketRadius = rocketRadius + self.airfoil = airfoil + self.distanceToCM = distanceToCM + self.cantAngle = cantAngle + self.rootChord = rootChord + self.span = span + self.name = name + self.d = d + self.Aref = Aref # Reference area + + return None + + @abstractmethod + def evaluateCenterOfPressure(self): + """Calculates and returns the finset's center of pressure + in relation to the rocket's center of dry mass. The center + of pressure position is saved and stored in a tuple. + + Parameters + ---------- + None + + Returns + ------- + self.cp : tuple + Tuple containing cpx, cpy, cpz. + """ + + pass + def evaluateLiftCoefficient(self): - """Calculates and returns the lift coefficient of the fin set. + """Calculates and returns the finset's lift coefficient. The lift coefficient is saved and returned. This function also calculates and saves the lift coefficient derivative for a single fin and the lift coefficient derivative for @@ -242,7 +327,7 @@ def evaluateLiftCoefficient(self): """ if not self.airfoil: # Defines clalpha2D as 2*pi for planar fins - clalpha2D = Function(lambda mach: 2 * np.pi / self._beta(mach)) + clalpha2D = Function(lambda mach: 2 * np.pi / self.__beta(mach)) else: # Defines clalpha2D as the derivative of the # lift coefficient curve for a specific airfoil @@ -259,7 +344,7 @@ def evaluateLiftCoefficient(self): clalpha2D_Mach0 *= 180 / np.pi # Correcting for compressible flow - clalpha2D = Function(lambda mach: clalpha2D_Mach0 / self._beta(mach)) + clalpha2D = Function(lambda mach: clalpha2D_Mach0 / self.__beta(mach)) # Diederich's Planform Correlation Parameter FD = 2 * np.pi * self.AR / (clalpha2D * np.cos(self.gamma_c)) @@ -272,27 +357,33 @@ def evaluateLiftCoefficient(self): * (self.Af / self.Aref) * np.cos(self.gamma_c) ) - / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)) + / (2 + FD(mach) * np.sqrt(1 + (2 / FD(mach)) ** 2)), + "Mach", + "Lift coefficient derivative for a single fin", ) # Lift coefficient derivative for a number of n fins corrected for Fin-Body interference self.clalphaMultipleFins = ( self.liftInterferenceFactor - * self._finNumCorrection(self.n) + * self.__finNumCorrection(self.n) * self.clalphaSingleFin ) # Function of mach number + self.clalphaMultipleFins.setInputs("Mach") + self.clalphaMultipleFins.setOutputs( + "Lift coefficient derivative for {:.0f} fins".format(self.n) + ) # Calculates clalpha * alpha self.cl = Function( lambda alpha, mach: alpha * self.clalphaMultipleFins(mach), ["Alpha (rad)", "Mach"], - "Cl", + "Lift coefficient", ) return self.cl - def evaluateRollCoefficients(self): - """Calculates and returns the fin set's roll coefficients. + def evaluateRollParameters(self): + """Calculates and returns the finset's roll coefficients. The roll coefficients are saved in a list. Parameters @@ -312,10 +403,12 @@ def evaluateRollCoefficients(self): clfDelta = ( self.rollForcingInterferenceFactor * self.n - * (self.Yma + self.radius) + * (self.Yma + self.rocketRadius) * self.clalphaSingleFin / self.d ) # Function of mach number + clfDelta.setInputs("Mach") + clfDelta.setOutputs("Roll moment forcing coefficient derivative") cldOmega = ( 2 * self.rollDampingInterferenceFactor @@ -325,42 +418,13 @@ def evaluateRollCoefficients(self): * self.rollGeometricalConstant / (self.Aref * self.d**2) ) # Function of mach number + cldOmega.setInputs("Mach") + cldOmega.setOutputs("Roll moment damping coefficient derivative") self.rollParameters = [clfDelta, cldOmega, self.cantAngleRad] return self.rollParameters - def setAttribute(self, name, value): - """Changes an existing attribute to a new value and - reruns the evaluate methods. - - Parameters - ---------- - name : string - Name of the attribute that will be changed. - value : any - value to which the attribute will be changed to. - - Returns - ------- - None - """ - # Changes attribute value - self.__dict__[name] = value - - # Add changed attribute to dict - if self.changingAttributeDict.get(name) != None: - self.changingAttributeDict[name].append(value) - else: - self.changingAttributeDict[name] = [value] - - # Rerun important calculations - self.evaluateCenterOfPressure() - self.evaluateLiftCoefficient() - self.evaluateRollCoefficients() - - return None - # Defines beta parameter - def _beta(_, mach): + def __beta(_, mach): """Defines a parameter that is often used in aerodynamic equations. It is commonly used in the Prandtl factor which corrects subsonic force coefficients for compressible flow. @@ -384,7 +448,7 @@ def _beta(_, mach): return np.sqrt(mach**2 - 1) # Defines number of fins factor - def _finNumCorrection(_, n): + def __finNumCorrection(_, n): """Calculates a correction factor for the lift coefficient of multiple fins. The specifics values are documented at: Niskanen, S. (2013). “OpenRocket technical documentation”. In: Development @@ -406,6 +470,22 @@ def _finNumCorrection(_, n): else: return n / 2 + @abstractmethod + def draw(): + """Draw the fin shape along with some important + information. These being, the center line, the + quarter line and the center of pressure position. + + Parameters + ---------- + None + + Returns + ------- + None + """ + pass + def geometricalInfo(self): """Prints out information about geometrical parameters of the fin set. @@ -418,7 +498,8 @@ def geometricalInfo(self): ------ None """ - print("\n Geometrical Parameters\n") + + print("\n\nGeometrical Parameters\n") if isinstance(self, TrapezoidalFins): print("Fin Type: Trapezoidal") print("Tip Chord: {:.3f} m".format(self.tipChord)) @@ -436,11 +517,9 @@ def geometricalInfo(self): "Roll Geometrical Constant: {:.3f} m".format(self.rollGeometricalConstant) ) - self.draw() - return None - def liftInfo(self): + def aerodynamicInfo(self): """Prints out information about lift parameters of the fin set. @@ -452,7 +531,7 @@ def liftInfo(self): ------ None """ - print("\nLift Information") + print("\n\nAerodynamic Information") print("----------------") print("Lift Interference Factor: {:.3f} m".format(self.liftInterferenceFactor)) print( @@ -503,8 +582,8 @@ def rollInfo(self): ) ) - self.rollParameters[0]() # TODO: improve this plot - self.rollParameters[1]() # TODO: improve this plot + self.rollParameters[0]() + self.rollParameters[1]() return None @@ -520,16 +599,18 @@ def allInfo(self): None """ - print("Fin set information\n\n") + print("Fin set information\n") + + self.draw() print("Basic Information\n") print("Number of Fins: {:.0f}".format(self.n)) - print("Rocket radius at self's position: {:.3f} m".format(self.radius)) + print("Rocket radius at self's position: {:.3f} m".format(self.rocketRadius)) print("Fin Distance to CM: {:.3f} m".format(self.distanceToCM)) self.geometricalInfo() - self.liftInfo() + self.aerodynamicInfo() self.rollInfo() return None @@ -542,56 +623,56 @@ class TrapezoidalFins(Fins): ---------- Geometrical attributes: - TrapezoidalFins.n : int + Fins.n : int Number of fins in fin set - TrapezoidalFins.radius : float + Fins.rocketRadius : float Radius of the rocket at the position of the fin set - TrapezoidalFins.airfoil : tuple + Fins.airfoil : tuple Tuple of two items. First is the airfoil lift curve. Second is the unit of the curve (radians or degrees) - TrapezoidalFins.distanceToCM : float + Fins.distanceToCM : float Fin set position relative to rocket unloaded center of mass, considering positive direction from center of mass to nose cone. - TrapezoidalFins.cantAngle : float + Fins.cantAngle : float Fins cant angle with respect to the rocket centerline, in degrees - TrapezoidalFins.changingAttributeDict : dict + Fins.changingAttributeDict : dict Dictionary that stores the name and the values of the attributes that may be changed during a simulation. Useful for control systems. - TrapezoidalFins.cantAngleRad : float + Fins.cantAngleRad : float Fins cant angle with respect to the rocket centerline, in radians - TrapezoidalFins.rootChord : float + Fins.rootChord : float Fin root chord in meters. - TrapezoidalFins.tipChord : float + Fins.tipChord : float Fin tip chord in meters. - TrapezoidalFins.span : float + Fins.span : float Fin span in meters. - TrapezoidalFins.name : string + Fins.name : string Name of fin set - TrapezoidalFins.sweepLength : float + Fins.sweepLength : float Fins sweep length in meters. By sweep length, understand the axial distance between the fin root leading edge and the fin tip leading edge measured parallel to the rocket centerline. - TrapezoidalFins.sweepAngle : float + Fins.sweepAngle : float Fins sweep angle with respect to the rocket centerline. Must be given in degrees. - TrapezoidalFins.d : float + Fins.d : float Diameter of the rocket at the position of the fin set - TrapezoidalFins.Aref : float + Fins.Aref : float Reference area of the rocket - TrapezoidalFins.Af : float + Fins.Af : float Area of the longitudinal section of each fin in the set - TrapezoidalFins.AR : float + Fins.AR : float Aspect ratio of each fin in the set - TrapezoidalFins.gamma_c : float + Fins.gamma_c : float Fin mid-chord sweep angle - TrapezoidalFins.Yma : float + Fins.Yma : float Span wise position of the mean aerodynamic chord - TrapezoidalFins.rollGeometricalConstant : float + Fins.rollGeometricalConstant : float Geometrical constant used in roll calculations - TrapezoidalFins.tau : float + Fins.tau : float Geometrical relation used to simplify lift and roll calculations - TrapezoidalFins.liftInterferenceFactor : float + Fins.liftInterferenceFactor : float Factor of Fin-Body interference in the lift coefficient """ @@ -602,7 +683,7 @@ def __init__( tipChord, span, distanceToCM, - radius, + rocketRadius, cantAngle=0, sweepLength=None, sweepAngle=None, @@ -626,7 +707,7 @@ def __init__( mass, considering positive direction from center of mass to nose cone. Consider the center point belonging to the top of the fins to calculate distance. - radius : int, float + rocketRadius : int, float Reference radius to calculate lift coefficient. If None, which is default, use rocket radius. Otherwise, enter the radius of the rocket in the section of the fins, as this impacts @@ -669,6 +750,17 @@ def __init__( None """ + super().__init__( + n, + rootChord, + span, + distanceToCM, + rocketRadius, + cantAngle, + airfoil, + name, + ) + # Check if sweep angle or sweep length is given if sweepLength is not None and sweepAngle is not None: raise ValueError("Cannot use sweepLength and sweepAngle together") @@ -680,31 +772,26 @@ def __init__( # Sweep length is given pass - # Compute auxiliary geometrical parameters - d = 2 * radius - Aref = np.pi * radius**2 # Reference area Yr = rootChord + tipChord Af = Yr * span / 2 # Fin area AR = 2 * span**2 / Af # Fin aspect ratio - gamma_c = np.arctan( - (sweepLength + 0.5 * tipChord - 0.5 * rootChord) / (span) - ) # Mid chord angle + gamma_c = np.arctan((sweepLength + 0.5 * tipChord - 0.5 * rootChord) / (span)) Yma = ( (span / 3) * (rootChord + 2 * tipChord) / Yr ) # Span wise coord of mean aero chord - rollGeometricalConstant = ( - (rootChord + 3 * tipChord) * span**3 - + 4 * (rootChord + 2 * tipChord) * radius * span**2 - + 6 * (rootChord + tipChord) * span * radius**2 - ) / 12 - # Fin–body interference correction parameters - tau = (span + radius) / radius + tau = (span + rocketRadius) / rocketRadius liftInterferenceFactor = 1 + 1 / tau + λ = tipChord / rootChord + # Parameters for Roll Moment. # Documented at: https://github.com/RocketPy-Team/RocketPy/blob/master/docs/technical/aerodynamics/Roll_Equations.pdf - λ = tipChord / rootChord + rollGeometricalConstant = ( + (rootChord + 3 * tipChord) * span**3 + + 4 * (rootChord + 2 * tipChord) * rocketRadius * span**2 + + 6 * (rootChord + tipChord) * span * rocketRadius**2 + ) / 12 rollDampingInterferenceFactor = 1 + ( ((tau - λ) / (tau)) - ((1 - λ) / (tau - 1)) * np.log(tau) ) / ( @@ -724,24 +811,12 @@ def __init__( + (8 / (tau - 1) ** 2) * np.log((tau**2 + 1) / (2 * tau)) ) - # Store values - self.n = n - self.radius = radius - self.airfoil = airfoil - self.distanceToCM = distanceToCM - self.cantAngle = cantAngle - self.changingAttributeDict = {} - self.rootChord = rootChord self.tipChord = tipChord - self.span = span - self.name = name self.sweepLength = sweepLength self.sweepAngle = sweepAngle - self.d = d - self.Aref = Aref # Reference area self.Yr = Yr self.Af = Af # Fin area - self.AR = AR # Fin aspect ratio + self.AR = AR # Aspect Ratio self.gamma_c = gamma_c # Mid chord angle self.Yma = Yma # Span wise coord of mean aero chord self.rollGeometricalConstant = rollGeometricalConstant @@ -753,8 +828,7 @@ def __init__( self.evaluateCenterOfPressure() self.evaluateLiftCoefficient() - - self.evaluateRollCoefficients() + self.evaluateRollParameters() def evaluateCenterOfPressure(self): """Calculates and returns the center of pressure of the fin set @@ -889,6 +963,8 @@ def draw(self): ax1.set_title("Trapezoidal Fin") ax1.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left") + plt.show() + return None @@ -899,54 +975,54 @@ class EllipticalFins(Fins): ---------- Geometrical attributes: - EllipticalFins.n : int + Fins.n : int Number of fins in fin set - EllipticalFins.radius : float + Fins.rocketRadius : float Radius of the rocket at the position of the fin set - EllipticalFins.airfoil : tuple + Fins.airfoil : tuple Tuple of two items. First is the airfoil lift curve. Second is the unit of the curve (radians or degrees) - EllipticalFins.distanceToCM : float + Fins.distanceToCM : float Fin set position relative to rocket unloaded center of mass, considering positive direction from center of mass to nose cone. - EllipticalFins.cantAngle : float + Fins.cantAngle : float Fins cant angle with respect to the rocket centerline, in degrees - EllipticalFins.changingAttributeDict : dict + Fins.changingAttributeDict : dict Dictionary that stores the name and the values of the attributes that may be changed during a simulation. Useful for control systems. - EllipticalFins.cantAngleRad : float + Fins.cantAngleRad : float Fins cant angle with respect to the rocket centerline, in radians - EllipticalFins.rootChord : float + Fins.rootChord : float Fin root chord in meters. - EllipticalFins.span : float + Fins.span : float Fin span in meters. - EllipticalFins.name : string + Fins.name : string Name of fin set - EllipticalFins.sweepLength : float + Fins.sweepLength : float Fins sweep length in meters. By sweep length, understand the axial distance between the fin root leading edge and the fin tip leading edge measured parallel to the rocket centerline. - EllipticalFins.sweepAngle : float + Fins.sweepAngle : float Fins sweep angle with respect to the rocket centerline. Must be given in degrees. - EllipticalFins.d : float + Fins.d : float Diameter of the rocket at the position of the fin set - EllipticalFins.Aref : float - Reference area of the rocket - EllipticalFins.Af : float - Area of the longitudinal section of each fin in the set - EllipticalFins.AR : float + Fins.Aref : float + Reference area of the rocket at the fin set position. + Fins.Af : float + Area of the longtudinal section of each fin in the set + Fins.AR : float Aspect ratio of each fin in the set - EllipticalFins.gamma_c : float + Fins.gamma_c : float Fin mid-chord sweep angle - EllipticalFins.Yma : float + Fins.Yma : float Span wise position of the mean aerodynamic chord - EllipticalFins.rollGeometricalConstant : float + Fins.rollGeometricalConstant : float Geometrical constant used in roll calculations - EllipticalFins.tau : float + Fins.tau : float Geometrical relation used to simplify lift and roll calculations - EllipticalFins.liftInterferenceFactor : float + Fins.liftInterferenceFactor : float Factor of Fin-Body interference in the lift coefficient """ @@ -956,7 +1032,7 @@ def __init__( rootChord, span, distanceToCM, - radius, + rocketRadius, cantAngle=0, airfoil=None, name="Fins", @@ -976,7 +1052,7 @@ def __init__( mass, considering positive direction from center of mass to nose cone. Consider the center point belonging to the top of the fins to calculate distance. - radius : int, float + rocketRadius : int, float Reference radius to calculate lift coefficient. If None, which is default, use rocket radius. Otherwise, enter the radius of the rocket in the section of the fins, as this impacts @@ -1019,9 +1095,18 @@ def __init__( None """ + super().__init__( + n, + rootChord, + span, + distanceToCM, + rocketRadius, + cantAngle, + airfoil, + name, + ) + # Compute auxiliary geometrical parameters - d = 2 * radius - Aref = np.pi * radius**2 # Reference area for coefficients Af = (np.pi * rootChord / 2 * span) / 2 # Fin area gamma_c = 0 # Zero for elliptical fins AR = 2 * span**2 / Af # Fin aspect ratio @@ -1031,37 +1116,41 @@ def __init__( rollGeometricalConstant = ( rootChord * span - * (3 * np.pi * span**2 + 32 * radius * span + 12 * np.pi * radius**2) + * ( + 3 * np.pi * span**2 + + 32 * rocketRadius * span + + 12 * np.pi * rocketRadius**2 + ) / 48 ) # Fin–body interference correction parameters - tau = (span + radius) / radius + tau = (span + rocketRadius) / rocketRadius liftInterferenceFactor = 1 + 1 / tau rollDampingInterferenceFactor = 1 + ( - (radius**2) + (rocketRadius**2) * ( 2 - * (radius**2) - * np.sqrt(span**2 - radius**2) + * (rocketRadius**2) + * np.sqrt(span**2 - rocketRadius**2) * np.log( - (2 * span * np.sqrt(span**2 - radius**2) + 2 * span**2) - / radius + (2 * span * np.sqrt(span**2 - rocketRadius**2) + 2 * span**2) + / rocketRadius ) - 2 - * (radius**2) - * np.sqrt(span**2 - radius**2) + * (rocketRadius**2) + * np.sqrt(span**2 - rocketRadius**2) * np.log(2 * span) + 2 * span**3 - - np.pi * radius * span**2 - - 2 * (radius**2) * span - + np.pi * radius**3 + - np.pi * rocketRadius * span**2 + - 2 * (rocketRadius**2) * span + + np.pi * rocketRadius**3 ) ) / ( 2 * (span**2) - * (span / 3 + np.pi * radius / 4) - * (span**2 - radius**2) + * (span / 3 + np.pi * rocketRadius / 4) + * (span**2 - rocketRadius**2) ) rollForcingInterferenceFactor = (1 / np.pi**2) * ( (np.pi**2 / 4) * ((tau + 1) ** 2 / tau**2) @@ -1078,18 +1167,6 @@ def __init__( ) # Store values - self.n = n - self.radius = radius - self.airfoil = airfoil - self.distanceToCM = distanceToCM - self.cantAngle = cantAngle - self.changingAttributeDict = {} - self.cantAngleRad = np.radians(cantAngle) - self.rootChord = rootChord - self.span = span - self.name = name - self.d = d - self.Aref = Aref # Reference area self.Af = Af # Fin area self.AR = AR # Fin aspect ratio self.gamma_c = gamma_c # Mid chord angle @@ -1102,7 +1179,7 @@ def __init__( self.evaluateCenterOfPressure() self.evaluateLiftCoefficient() - self.evaluateRollCoefficients() + self.evaluateRollParameters() return None @@ -1195,6 +1272,8 @@ def draw(self): ax1.set_title("Elliptical Fin") ax1.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left") + plt.show() + return None @@ -1215,7 +1294,7 @@ class Tail: longitudinal axis. Has the unit of meters. Tail.distanceToCM : int, float Distance from the center of dry mass to the center of the tail. - Tail.radius: int, float + Tail.rocketRadius: int, float The radius of the rocket's body at the tail's position. Tail.name : str Name of the tail. Default is 'Tail'. @@ -1245,7 +1324,7 @@ class Tail: """ def __init__( - self, topRadius, bottomRadius, length, distanceToCM, radius, name="Tail" + self, topRadius, bottomRadius, length, distanceToCM, rocketRadius, name="Tail" ): """Initializes the tail object by computing and storing the most important values. @@ -1261,7 +1340,7 @@ def __init__( Length of the tail. distanceToCM : int, float Distance from the center of dry mass to the center of the tail. - radius : int, float + rocketRadius : int, float The radius of the rocket's body at the tail's position. name : str Name of the tail. Default is 'Tail'. @@ -1277,14 +1356,11 @@ def __init__( self.length = length self.distanceToCM = distanceToCM self.name = name - self.radius = radius + self.rocketRadius = rocketRadius # Calculate ratio between top and bottom radius r = topRadius / bottomRadius - # Retrieve reference radius - rref = self.radius - # Calculate tail slant length self.slantLength = np.sqrt( (self.length) ** 2 + (self.topRadius - self.bottomRadius) ** 2 @@ -1301,7 +1377,7 @@ def __init__( cpz = distanceToCM + (length / 3) * (1 + (1 - r) / (1 - r**2)) # Calculate clalpha - clalpha = -2 * (1 - r ** (-2)) * (topRadius / rref) ** 2 + clalpha = -2 * (1 - r ** (-2)) * (topRadius / rocketRadius) ** 2 cl = Function( lambda alpha, mach: clalpha * alpha, ["Alpha (rad)", "Mach"], @@ -1331,7 +1407,7 @@ def geometricInfo(self): print(f"Tail Bottom Radius: {self.bottomRadius:.3f} m") print(f"Tail Length: {self.length:.3f} m") print(f"Tail Distance to Center of Dry Mass: {self.distanceToCM:.3f} m") - print(f"Rocket body radius at tail position: {2*self.radius:.3f} m") + print(f"Rocket body radius at tail position: {2*self.rocketRadius:.3f} m") print(f"Tail Slant Length: {self.slantLength:.3f} m") print(f"Tail Surface Area: {self.surfaceArea:.6f} m^2") @@ -1341,7 +1417,6 @@ def aerodynamicInfo(self): print(f"\nTail name: {self.name}") print(f"Tail Center of Pressure: {self.cp}") - # print(f"Tail Lift Coefficient: {self.cl}") print(f"Tail Lift Coefficient Slope: {self.clalpha}") print("Tail Lift Coefficient as a function of Alpha and Mach:") self.cl() diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py index ab3c0a478..a3270be44 100644 --- a/rocketpy/Flight.py +++ b/rocketpy/Flight.py @@ -1386,6 +1386,8 @@ def uDot(self, t, u, postProcessing=False): # Calculate lift and moment for each component of the rocket for aerodynamicSurface in self.rocket.aerodynamicSurfaces: compCp = aerodynamicSurface.cp[2] + surfaceRadius = aerodynamicSurface.rocketRadius + referenceArea = np.pi * surfaceRadius**2 # Component absolute velocity in body frame compVxB = vxB + compCp * omega2 compVyB = vyB - compCp * omega1 @@ -1413,9 +1415,10 @@ def uDot(self, t, u, postProcessing=False): if -1 * compStreamVzBn < 1: compAttackAngle = np.arccos(-compStreamVzBn) cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) + cLift = aerodynamicSurface.cl(compAttackAngle, freestreamMach) # Component lift force magnitude compLift = ( - 0.5 * rho * (compStreamSpeed**2) * self.rocket.area * cLift + 0.5 * rho * (compStreamSpeed**2) * referenceArea * cLift ) # Component lift force components liftDirNorm = (compStreamVxB**2 + compStreamVyB**2) ** 0.5 @@ -1432,16 +1435,16 @@ def uDot(self, t, u, postProcessing=False): Clfdelta, Cldomega, cantAngleRad = aerodynamicSurface.rollParameters M3f = ( (1 / 2 * rho * freestreamSpeed**2) - * self.rocket.area + * referenceArea * 2 - * self.rocket.radius + * surfaceRadius * Clfdelta(freestreamMach) * cantAngleRad ) M3d = ( (1 / 2 * rho * freestreamSpeed) - * self.rocket.area - * (2 * self.rocket.radius) ** 2 + * referenceArea + * (2 * surfaceRadius) ** 2 * Cldomega(freestreamMach) * omega3 / 2 diff --git a/rocketpy/Rocket.py b/rocketpy/Rocket.py index c6dda8666..095609bf3 100644 --- a/rocketpy/Rocket.py +++ b/rocketpy/Rocket.py @@ -424,7 +424,7 @@ def addNose(self, length, kind, distanceToCM, name="Nose Cone"): Object of the Rocket class. """ # Create a nose as an object of NoseCone class - nose = NoseCone(length, kind, distanceToCM, name) + nose = NoseCone(length, kind, distanceToCM, self.radius, name) # Add nose to the list of aerodynamic surfaces self.aerodynamicSurfaces.append(nose) From f7c7a193264af70a7eece112c997f377f6ec5ea9 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Thu, 3 Nov 2022 08:06:04 -0300 Subject: [PATCH 17/17] ENH: add try: except: to roll --- rocketpy/Flight.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py index a3270be44..8be8a2342 100644 --- a/rocketpy/Flight.py +++ b/rocketpy/Flight.py @@ -1431,7 +1431,7 @@ def uDot(self, t, u, postProcessing=False): M1 -= (compCp + a) * compLiftYB M2 += (compCp + a) * compLiftXB # Calculates Roll Moment - if aerodynamicSurface.name == "Fins": + try: Clfdelta, Cldomega, cantAngleRad = aerodynamicSurface.rollParameters M3f = ( (1 / 2 * rho * freestreamSpeed**2) @@ -1450,6 +1450,8 @@ def uDot(self, t, u, postProcessing=False): / 2 ) M3 += M3f - M3d + except AttributeError: + pass # Calculate derivatives # Angular acceleration alpha1 = (