def getAppliedForce(self, rocketState, time, environment, CG): Aref = self.rocket.Aref Mach = AeroParameters.getMachNumber(rocketState, environment) # Normal Force -------------------------------------------------------------------------------- # TODO: Account for rate of pitch/yaw rotation in AOA calculation? Or do separate Pitch/Yaw damping moments? AOA = AeroParameters.getTotalAOA(rocketState, environment) normalForceCoefficient = AeroFunctions.Barrowman_GetCN( AOA, Aref, 0, self.baseArea) # Drag Force --------------------------------------------------------------------------------------------- #### Skin Friction #### skinFrictionDragCoefficient, rollDampingMoment = AeroFunctions.getCylindricalSkinFrictionDragCoefficientAndRollDampingMoment(rocketState, environment, self.length, Mach, \ self.surfaceRoughness, self.wettedArea, Aref, self.rocket.finenessRatio, self.rocket.fullyTurbulentBL) #### Pressure #### pressureDragCoefficient = self._getCd_Pressure(Mach) totalDragCoefficient = pressureDragCoefficient + skinFrictionDragCoefficient # Damping moments -------------------------------------------------------------------------------------- # Roll damping due to skin friction dampingMoments = rollDampingMoment # Combine forces and return total ------------------------------------------------------------------------------------- return AeroFunctions.forceFromCdCN(rocketState, environment, totalDragCoefficient, normalForceCoefficient, self.CPLocation, Aref, moment=dampingMoments)
def getAppliedForce(self, rocketState, time, environment, CG): Aref = self.rocket.Aref # Normal Force ---------------------------------------------------------------------------------------- AOA = AeroParameters.getTotalAOA(rocketState, environment) # Niskanen Eqn 3.26 - originally from Galejs normalForceCoefficient = 1.1 * (math.sin(AOA))**2 normalForceCoefficient *= self.planformArea / Aref # Drag ----------------------------------------------------------------------------------------------- # Skin Friction Mach = AeroParameters.getMachNumber(rocketState, environment) skinFrictionDragCoefficient, rollDampingMoment = AeroFunctions.getCylindricalSkinFrictionDragCoefficientAndRollDampingMoment(rocketState, environment, self.length, Mach, self.surfaceRoughness, \ self.wettedArea, Aref, self.rocket.finenessRatio, self.rocket.fullyTurbulentBL) #There is no pressure drag associated with bodytubes - skin friction drag is total drag # Damping moments -------------------------------------------------------------------------------------- dampingMoments = self._computeLongitudinalDampingMoments(rocketState, environment, CG) # Roll damping due to skin friction dampingMoments += rollDampingMoment # Compute & dimensionalize total------------------------------------------------------------------------- return AeroFunctions.forceFromCdCN(rocketState, environment, skinFrictionDragCoefficient, normalForceCoefficient, self.CPLocation, Aref, moment=dampingMoments)
def getAppliedForce(self, rocketState, time, environment, CG) -> ForceMomentSystem: Mach = AeroParameters.getMachNumber(rocketState, environment) Aref = self.rocket.Aref #### Normal Force #### AOA = AeroParameters.getTotalAOA(rocketState, environment) CN = AeroFunctions.Barrowman_GetCN(AOA, Aref, self.topArea, self.bottomArea) #### Pressure Drag #### if self.startDiameter > self.endDiameter: # Pressure base drag Cd_base = AeroFunctions.getBaseDragCoefficient(Mach) Cd_pressure = Cd_base * self.CdAdjustmentFactor else: # Pressure drag calculated like a nose cone if Mach < 1: # Niskanen pg. 48 eq. 3.87 - Power law interpolation Cd_pressure = self.SubsonicCdPolyCoeffs[ 0] * Mach**self.SubsonicCdPolyCoeffs[1] elif Mach > 1 and Mach < 1.3: # Interpolate in transonic region - derived from Niskanen Appendix B, Eqns B.3 - B.6 Cd_pressure = self.TransonicCdPolyCoeffs[0] + self.TransonicCdPolyCoeffs[1]*Mach + \ self.TransonicCdPolyCoeffs[2]*Mach**2 + self.TransonicCdPolyCoeffs[3]*Mach**3 else: Cd_pressure = getSupersonicPressureDragCoeff_Hoerner( self.coneHalfAngle, Mach) # Make reference are the rocket's, not this objects Cd_pressure *= self.frontalArea / Aref #### Skin Friction Drag #### if self.wettedArea == 0: skinFrictionDragCoefficient = 0 rollDampingMoment = Vector(0, 0, 0) else: skinFrictionDragCoefficient, rollDampingMoment = AeroFunctions.getCylindricalSkinFrictionDragCoefficientAndRollDampingMoment(rocketState, environment, \ self.length, Mach, self.surfaceRoughness, self.wettedArea, Aref, self.rocket.finenessRatio, self.rocket.fullyTurbulentBL) #### Total Drag #### Cd = Cd_pressure + skinFrictionDragCoefficient #### Assemble & return final force object #### return AeroFunctions.forceFromCdCN(rocketState, environment, Cd, CN, self.CPLocation, Aref, moment=rollDampingMoment)
def getAppliedForce(self, rocketState, time, environment, CG) -> ForceMomentSystem: Mach = AeroParameters.getMachNumber(rocketState, environment) Aref = self.rocket.Aref #### Normal Force #### AOA = AeroParameters.getTotalAOA(rocketState, environment) CN = AeroFunctions.Barrowman_GetCN(AOA, Aref, self.topArea, self.bottomArea) #### Pressure Drag #### Cd_base = AeroFunctions.getBaseDragCoefficient(Mach) Cd_pressure = Cd_base * self.CdAdjustmentFactor Cd_pressure *= self.frontalArea / self.rocket.Aref noEngine = (self.stage.engineShutOffTime == None) if noEngine or time > self.stage.engineShutOffTime: # Add base drag if engine is off Cd_pressure += Cd_base * self.bottomArea / Aref #### Skin Friction Drag #### if self.wettedArea == 0: skinFrictionDragCoefficient = 0 rollDampingMoment = Vector(0, 0, 0) else: skinFrictionDragCoefficient, rollDampingMoment = AeroFunctions.getCylindricalSkinFrictionDragCoefficientAndRollDampingMoment(rocketState, environment, \ self.length, Mach, self.surfaceRoughness, self.wettedArea, Aref, self.rocket.finenessRatio, self.rocket.fullyTurbulentBL) #### Total Drag #### Cd = Cd_pressure + skinFrictionDragCoefficient #### Assemble & return final force object #### return AeroFunctions.forceFromCdCN(rocketState, environment, Cd, CN, self.CPLocation, Aref, moment=rollDampingMoment)
def _getAppliedForce(self, time, state): ''' Get the total force currently being experienced by the rocket, used by self.rigidBody to calculate the rocket's acceleration ''' ### Precomputations and Logging ### environment = self._getEnvironmentalConditions(time, state) # Get and log current air/wind properties rocketInertia = self.getInertia(time, state) # Get and log current rocket inertia if self.derivativeEvaluationLog is not None: self.derivativeEvaluationLog.newLogRow(time) self.derivativeEvaluationLog.logValue("Position(m)", state.position) self.derivativeEvaluationLog.logValue("Velocity(m/s)", state.velocity) ### Component Forces ### if not self.isUnderChute: # Precompute and log Mach = AeroParameters.getMachNumber(state, environment) unitRe = AeroParameters.getReynoldsNumber(state, environment, 1.0) AOA = AeroParameters.getTotalAOA(state, environment) rollAngle = AeroParameters.getRollAngle(state, environment) if self.derivativeEvaluationLog is not None: self.derivativeEvaluationLog.logValue("Mach", Mach) self.derivativeEvaluationLog.logValue("UnitRe", unitRe) self.derivativeEvaluationLog.logValue("AOA(deg)", math.degrees(AOA)) self.derivativeEvaluationLog.logValue("RollAngle(deg)", rollAngle) self.derivativeEvaluationLog.logValue("OrientationQuaternion", state.orientation) self.derivativeEvaluationLog.logValue("AngularVelocity(rad/s)", state.angularVelocity) # This function will be the inherited function CompositeObject.getAppliedForce componentForces = self.getAppliedForce(state, time, environment, rocketInertia.CG) else: # When under chute, neglect forces from other components componentForces = self.recoverySystem.getAppliedForce(state, time, environment, Vector(0,0,-1)) # Log the recovery system's applied force (Normally handled in CompositeObject.getAppliedForce) if hasattr(self.recoverySystem, "forcesLog"): self.recoverySystem.forcesLog.append(componentForces.force) self.recoverySystem.momentsLog.append(componentForces.moment) # Move Force-Moment system to rocket CG componentForces = componentForces.getAt(rocketInertia.CG) ### Gravity ### gravityForce = self.environment.getGravityForce(rocketInertia, state) totalForce = componentForces + gravityForce ### Launch Rail ### totalForce = self.environment.applyLaunchTowerForce(state, time, totalForce) if self.derivativeEvaluationLog is not None: self.derivativeEvaluationLog.logValue("Wind(m/s)", environment.Wind) self.derivativeEvaluationLog.logValue("AirDensity(kg/m^3)", environment.Density) self.derivativeEvaluationLog.logValue("CG(m)", rocketInertia.CG) self.derivativeEvaluationLog.logValue("MOI(kg*m^2)", rocketInertia.MOI) self.derivativeEvaluationLog.logValue("Mass(kg)", rocketInertia.mass) CPZ = AeroFunctions._getCPZ(componentForces) self.derivativeEvaluationLog.logValue("CPZ(m)", CPZ) self.derivativeEvaluationLog.logValue("AeroF(N)", componentForces.force) self.derivativeEvaluationLog.logValue("AeroM(Nm)", componentForces.moment) self.derivativeEvaluationLog.logValue("GravityF(N)", gravityForce.force) self.derivativeEvaluationLog.logValue("TotalF(N)", totalForce.force) return totalForce
def test_getMachNumber(self): environment = self.environment.getAirProperties(Vector(0,0,0)) # Assumes gamma=1.4, R=287 self.assertAlmostEqual(AeroParameters.getMachNumber(self.rocketState1, environment), 0.587781235)
def _barrowmanAeroFunc(self, rocketState, time, environment, precomputedData, CG=Vector(0, 0, 0), finDeflectionAngle=None): ''' Precomputed Data is a named tuple (PreComputedFinAeroData) which contains data/results from the parts of the fin aerodynamics calculation that are common to all fins in a FinSet (drag calculations mostly). These calculations are performed at the FinSet level. Only the parts of the Fin Aero Computation that change from fin to fin (normal force mostly, since different fins can have different angles of attack) are computed here ''' Mach = AeroParameters.getMachNumber(rocketState, environment) dynamicPressure = AeroParameters.getDynamicPressure( rocketState, environment) if finDeflectionAngle == None: finDeflectionAngle = self.finAngle # Adjusted by parent finset during each timestep, when the FinSet is controlled # Unpack precomputedData airVelRelativeToFin, CPXPos, totalDragCoefficient = precomputedData #### Compute normal force----------------------------------------------------------------------------------------------------------------- # Find fin normal vector after fin deflection finDeflectionRotation = Quaternion( axisOfRotation=self.spanwiseDirection, angle=radians(finDeflectionAngle)) finNormal = finDeflectionRotation.rotate(self.undeflectedFinNormal) # Get the tangential velocity component vector, per unit radial distance from the rocket centerline rollAngVel = AngularVelocity(0, 0, rocketState.angularVelocity.Z) unitSpanTangentialAirVelocity = rollAngVel.crossProduct( self.spanwiseDirection) * (-1) def subsonicNormalForce(Mach): # Subsonic linear method tempBeta = AeroParameters.getBeta(Mach) CnAlpha = getFinCnAlpha_Subsonic_Barrowman(self.span, self.planformArea, tempBeta, self.midChordSweep) return getSubsonicFinNormalForce(airVelRelativeToFin, unitSpanTangentialAirVelocity, finNormal, self.spanwiseDirection, self.CPSpanWisePosition.length(), CnAlpha, self) def supersonicNormalForce(Mach): # Supersonic Busemann method gamma = AeroFunctions.getGamma() tempBeta = AeroParameters.getBeta(Mach) K1, K2, K3, Kstar = getBusemannCoefficients(Mach, tempBeta, gamma) # Mach Cone coords machAngle = asin(1 / Mach) machCone_negZPerRadius = 1 / tan(machAngle) machConeEdgeZPos = [] outerRadius = self.spanSliceRadii[-1] for i in range(len(self.spanSliceRadii)): machConeAtCurrentRadius = ( outerRadius - self.spanSliceRadii[i] ) * machCone_negZPerRadius + self.tipPosition machConeEdgeZPos.append(machConeAtCurrentRadius) return getSupersonicFinNormalForce( airVelRelativeToFin, unitSpanTangentialAirVelocity, finNormal, machConeEdgeZPos, self.spanwiseDirection, self.CPSpanWisePosition.length(), K1, K2, K3, Kstar, self) if Mach <= 0.8: normalForceMagnitude, finMoment = subsonicNormalForce(Mach) elif Mach < 1.4: # Interpolate in transonic region # TODO: Do this with less function evaluations? Perhaps precompute AOA and Mach combinations and simply interpolate? Lazy precompute? Cython? x1, x2 = 0.8, 1.4 # Start, end pts of interpolated region dx = 0.001 # Find normal force and derivative at start of interpolation interval f_x1, m_x1 = subsonicNormalForce(x1) f_x12, m_x12 = subsonicNormalForce(x1 + dx) # Find normal force and derivative at end of interpolation interval f_x2, m_x2 = supersonicNormalForce(x2) f_x22, m_x22 = supersonicNormalForce(x2 + dx) normalForceMagnitude = Interpolation.cubicInterp( Mach, x1, x2, f_x1, f_x2, f_x12, f_x22, dx) finMoment = Interpolation.cubicInterp(Mach, x1, x2, m_x1, m_x2, m_x12, m_x22, dx) else: normalForceMagnitude, finMoment = supersonicNormalForce(Mach) # Complete redimensionalization of normal force coefficients by multiplying by dynamic pressure # Direct the normal force along the fin's normal direction normalForce = normalForceMagnitude * dynamicPressure * finNormal finMoment *= dynamicPressure #### Get axial force----------------------------------------------------------------------------------------------------------------------- avgAOA = getFinSliceAngleOfAttack( self.spanSliceRadii[round(len(self.spanSliceAreas) / 2)], airVelRelativeToFin, unitSpanTangentialAirVelocity, finNormal, self.spanwiseDirection, self.stallAngle) # Approximate average fin AOA totalAxialForceCoefficient = AeroFunctions.getDragToAxialForceFactor( avgAOA) * totalDragCoefficient axialForceMagnitude = totalAxialForceCoefficient * self.rocket.Aref * dynamicPressure axialForceDirection = self.spanwiseDirection.crossProduct(finNormal) axialForce = axialForceDirection * axialForceMagnitude #### Get CP Location ---------------------------------------------------------------------------------------------------------------------- CPChordWisePosition = self.position - Vector( 0, 0, CPXPos ) # Ignoring the change in CP position due to fin deflection for now globalCP = self.CPSpanWisePosition + CPChordWisePosition #### Assemble total force moment system objects-------------------------------------------------------------------------------------------- totalForce = normalForce + axialForce return ForceMomentSystem(totalForce, globalCP, moment=Vector(0, 0, finMoment)), globalCP
def _getPreComputedFinAeroData(self, rocketState, environment, CG): #General Info --------------------------------------------------------------------------------------------------------------------- Aref = self.rocket.Aref Mach = AeroParameters.getMachNumber(rocketState, environment) dynamicPressure = AeroParameters.getDynamicPressure( rocketState, environment) # Skin Friction Drag ------------------------------------------------------------------------------------------------------------- skinFrictionCoefficient = AeroFunctions.getSkinFrictionCoefficient( rocketState, environment, self.MACLength, Mach, self.surfaceRoughness, self.rocket.fullyTurbulentBL) # Adjust to the rocket reference area (skinFrictionCoefficient is based on wetted area) skinFrictionDragCoefficient = skinFrictionCoefficient * ( self.wettedArea / Aref) # Correct for additional surface area due to fin thickness - Niskanen Eqn 3.85 skinFrictionDragCoefficient *= (1 + 2 * self.thickness / self.MACLength) # Pressure Drag ------------------------------------------------------------------------------------------------------------------ # Leading edge drag coefficient if self.leadingEdgeShape == "Round": leadingEdgeCd = AeroFunctions.getCylinderCrossFlowCd_ZeroBaseDrag( Mach) LEthickness = self.leadingEdgeRadius * 2 elif self.leadingEdgeShape == "Blunt": leadingEdgeCd = AeroFunctions.getBluntBodyCd_ZeroBaseDrag(Mach) LEthickness = self.leadingEdgeThickness # Adjust for leading edge angle and convert reference area to rocket reference area - Barrowman Eqn 4-22 leadingEdgeCdAdjustmentFactor = LEthickness * self.span * cos( self.sweepAngle)**2 / Aref leadingEdgeCd *= leadingEdgeCdAdjustmentFactor # Trailing edge drag coefficient - simpler method from Niskanen section 3.4.4. Corrected to use only the trailing edge area, not the full fin frontal area # more intricate method available in Barrowman baseDragCd = AeroFunctions.getBaseDragCoefficient(Mach) if self.trailingEdgeShape == "Tapered": TEthickness = 0 # Zero base drag elif self.trailingEdgeShape == "Round": TEthickness = self.trailingEdgeRadius # 0.5 base drag elif self.trailingEdgeShape == "Blunt": TEthickness = self.trailingEdgeThickness # Full base drag # Convert to standard rocket reference Area trailingEdgeCd = baseDragCd * self.span * TEthickness / Aref # Thickness / Wave Drag #TODO: This section doesn't seem to be working quite right if Mach <= 1: # Thickness drag, subsonic thicknessDrag = 4*skinFrictionCoefficient*((self.thickness/self.rootChord)*cos(self.midChordSweep) + \ (30 * (self.thickness/self.rootChord)**4 * cos(self.midChordSweep)**2) / \ (self.subsonicFinThicknessK - Mach**2 * cos(self.midChordSweep)**2)**(3/2)) else: # Supersonic wave drag # Using simplistic method from Hoerner - assumes diamond profile (pg 17-12, Eqn 29) # TODO: Implement method from Barrowman's FIN program thicknessDrag = 2.3 * self.aspectRatio * (self.thickness / self.MACLength)**2 thicknessDrag *= self.planformArea / Aref pressureDragCoefficient = leadingEdgeCd + trailingEdgeCd + thicknessDrag # Total Drag -------------------------------------------------------------------------------------------------------------------- totalDragCoefficient = pressureDragCoefficient + skinFrictionDragCoefficient localFrameRocketVelocity = AeroParameters.getLocalFrameAirVel( rocketState, environment) axialPositionRelCG = self.position - CG finVelocityDueToRocketPitchYaw = rocketState.angularVelocity.crossProduct( axialPositionRelCG) airVelRelativeToFin = localFrameRocketVelocity - finVelocityDueToRocketPitchYaw # The negative puts it in the wind frame CPXPos = self._getCPXPos(Mach) #### Transfer info to fins #### return PreComputedFinAeroData(airVelRelativeToFin, CPXPos, totalDragCoefficient)