Source code for RCAIDE.Library.Methods.Stability.Common.compute_dynamic_flight_modes

# RCAIDE/Library/Methods/Stability/compute_dynamic_flight_modes.py
# 
# 
# Created:  Apr 2024, M. Clarke

# ----------------------------------------------------------------------------------------------------------------------
#  IMPORT
# ----------------------------------------------------------------------------------------------------------------------

# RCAIDE imports
import RCAIDE 

# python imports 
import numpy as np 

# ----------------------------------------------------------------------------------------------------------------------
#  compute_dynamic_flight_modes
# ----------------------------------------------------------------------------------------------------------------------
[docs] def compute_dynamic_flight_modes(state,settings,vehicle): """This function follows the stability axis EOM derivation in Blakelock to return the vehicle's dynamic modes and state space Assumptions: Linerarized Equations are used following the reference below Source: Automatic Control of Aircraft and Missiles by J. Blakelock Pg 23 and 117 Dynanmics of Flight Stability and Control by Bernard Edkin and Llyod Reid Chapter 5, Inputs: conditions.aerodynamics conditions.static_stability Outputs: conditions.dynamic_stability.LatModes conditions.dynamic_stability.LongModes Properties Used: N/A """ conditions = state.conditions AoA = conditions.aerodynamics.angles.alpha n_cpts = state.numerics.number_of_control_points S_ref = vehicle.reference_area c_ref = vehicle.reference_chord b_ref = vehicle.reference_span if (np.count_nonzero(vehicle.mass_properties.moments_of_inertia.tensor) > 0) and (np.all(np.isnan(AoA)) != True): g = conditions.freestream.gravity rho = conditions.freestream.density u0 = conditions.freestream.velocity qDyn0 = conditions.freestream.dynamic_pressure theta0 = np.arctan(conditions.frames.inertial.velocity_vector[:,2]/conditions.frames.inertial.velocity_vector[:,0])[:,None] SS = conditions.static_stability SSD = SS.derivatives DS = conditions.dynamic_stability Ixx = conditions.weights.vehicle.moments_of_inertia_Ixx Ixz = conditions.weights.vehicle.moments_of_inertia_Ixz Iyy = conditions.weights.vehicle.moments_of_inertia_Iyy Izx = conditions.weights.vehicle.moments_of_inertia_Izx Izz = conditions.weights.vehicle.moments_of_inertia_Izz m = conditions.weights.vehicle.mass if np.all(conditions.static_stability.spiral_criteria) == 0: conditions.static_stability.spiral_criteria = SSD.CL_beta*SSD.CN_r / (SSD.CL_r*SSD.CN_beta) ## Build longitudinal EOM A Matrix (stability axis) ALon = np.zeros((n_cpts,4,4)) BLon = np.zeros((n_cpts,4,1)) # Elevator effectiveness for wing in vehicle.wings: if isinstance(wing,RCAIDE.Library.Components.Wings.Horizontal_Tail): horizontal_tail = wing if isinstance(wing,RCAIDE.Library.Components.Wings.Main_Wing): main_wing = wing if isinstance(wing,RCAIDE.Library.Components.Wings.Blended_Wing_Body): main_wing = wing horizontal_tail = wing # unpack unit conversions a_t = 2 * np.pi # dCL_t_dalphat l_t = (horizontal_tail.origin[0][0] +horizontal_tail.aerodynamic_center[0]) - vehicle.mass_properties.center_of_gravity[0][0] # disstance from CG to tail AC S_t = horizontal_tail.areas.reference # tail area S = main_wing.areas.reference # wing area l_bar_t = (horizontal_tail.origin[0][0] + horizontal_tail.aerodynamic_center[0]) -(main_wing.origin[0][0] + main_wing.aerodynamic_center[0]) # distance from AC of main wing to tail AC V_H = ( l_bar_t *S_t ) /(c_ref *S) # tail volume dEpsilon_dalpha = 0.3 SSD.CZ_alpha_dot = a_t * dEpsilon_dalpha * (l_t /u0) * (S_t / S) SSD.CM_alpha_dot = -a_t * V_H * dEpsilon_dalpha* (l_t /u0) Cw = m * g / (qDyn0 * S_ref) Xu = rho * u0 * S_ref * Cw * np.sin(theta0) + 0.5 * rho * u0 * S_ref * SSD.CX_u Xw = 0.5 * rho * u0 * S_ref * SSD.CX_alpha Zu = -rho * u0 * S_ref * Cw * np.cos(theta0) + 0.5 * rho * u0 * S_ref * SSD.CZ_u Zw = 0.5 * rho * u0 * S_ref * SSD.CZ_alpha Zq = 0.25 * rho * u0 * c_ref * S_ref * SSD.CZ_q Mu = 0.5 * rho * u0 * c_ref * S_ref * SSD.CM_u Mw = 0.5 * rho * u0 * c_ref * S_ref * SSD.CM_alpha Mq = 0.25 * rho * u0 * c_ref * c_ref * S_ref * SSD.CM_q ZwDot = 0.25 * rho * c_ref * S_ref * SSD.CZ_alpha_dot MwDot = 0.25 * rho * c_ref * S_ref * SSD.CM_alpha_dot ALon[:,0,0] = (Xu / m).T[0] ALon[:,0,1] = (Xw / m).T[0] ALon[:,0,3] = (-g * np.cos(theta0)).T[0] ALon[:,1,0] = (Zu / (m - ZwDot)).T[0] ALon[:,1,1] = (Zw / (m - ZwDot)).T[0] ALon[:,1,2] = ((Zq + (m * u0)) / (m - ZwDot) ).T[0] ALon[:,2,0] = ((Mu + MwDot * Zu / (m - ZwDot)) / Iyy).T[0] ALon[:,2,1] = ((Mw + MwDot * Zw / (m - ZwDot)) / Iyy).T[0] ALon[:,2,2] = ((Mq + MwDot * (Zq + m * u0) / (m - ZwDot)) / Iyy ).T[0] ALon[:,2,3] = (-MwDot * m * g * np.sin(theta0) / (Iyy * (m - ZwDot))).T[0] ALon[:,3,0] = 0 ALon[:,3,1] = 0 ALon[:,3,2] = 1 ALon[:,3,3] = 0 ele = conditions.control_surfaces.elevator.static_stability.coefficients Xe = 0 # Neglect Ze = 0.5 * rho * u0 * u0 * S_ref * ele.lift Me = 0.5 * rho * u0 * u0 * S_ref * c_ref * ele.M BLon[:,0,0] = Xe / m[:, 0] BLon[:,1,0] = (Ze / (m - ZwDot)).T[0] BLon[:,2,0] = (Me / Iyy + MwDot / Iyy * Ze / (m - ZwDot)).T[0] BLon[:,3,0] = 0 # Look at eigenvalues and eigenvectors LonModes = np.zeros((n_cpts,4), dtype = complex) phugoidFreqHz = np.zeros((n_cpts,1)) phugoidDamping = np.zeros((n_cpts,1)) phugoidTimeDoubleHalf = np.zeros((n_cpts,1)) shortPeriodFreqHz = np.zeros((n_cpts,1)) shortPeriodDamping = np.zeros((n_cpts,1)) shortPeriodTimeDoubleHalf = np.zeros((n_cpts,1)) try: LonModes , V = np.linalg.eig(ALon) phugoidInd = np.argmax(LonModes,axis=1) Ind = np.arange(n_cpts) phugoidFreqHz = np.atleast_2d(abs(LonModes[Ind, phugoidInd]) / (2 * np.pi)).T phugoidDamping = np.atleast_2d(np.sqrt(1/ (1 + ( LonModes[Ind,phugoidInd].imag/ LonModes[Ind,phugoidInd].real )**2 ))).T phugoidTimeDoubleHalf = np.log(2) / abs(2 * np.pi * phugoidFreqHz * phugoidDamping) # Find short period shortPeriodInd = np.argmin(LonModes,axis=1) shortPeriodFreqHz = np.atleast_2d(abs(LonModes[Ind, shortPeriodInd]) / (2 * np.pi)).T shortPeriodDamping = np.atleast_2d(np.sqrt(1/ (1 + (LonModes[Ind, shortPeriodInd].imag/LonModes[Ind,shortPeriodInd].real)**2 ))).T shortPeriodTimeDoubleHalf = np.log(2) / abs(2 * np.pi * shortPeriodFreqHz * shortPeriodDamping) except: pass ## Build lateral EOM A Matrix (stability axis) ALat = np.zeros((n_cpts,4,4)) BLat = np.zeros((n_cpts,4,1)) # Need to compute Ixx, Izz, and Ixz as a function of alpha. Which alpha? I wouldn't expect this to change. R = np.zeros((n_cpts,2,2)) modI = np.zeros((n_cpts,2,2)) R[:,0, 0] = np.cos(AoA[:, 0]) R[:,0, 1] = - np.sin(AoA[:, 0]) R[:,1, 0] = np.sin( AoA[:, 0]) R[:,1, 1] = np.cos(AoA[:, 0]) modI[:,0, 0] = Ixx[:, 0] modI[:,0, 1] = Ixz[:, 0] modI[:,1, 0] = Izx[:, 0] modI[:,1, 1] = Izz[:, 0] INew = R * modI * np.transpose(R, axes = (0,2,1)) IxxStab = INew[:,0,0] IxzStab = -INew[:,0,1] IzzStab = INew[:,1,1] Ixp = np.atleast_2d((IxxStab * IzzStab - IxzStab**2) / IzzStab).T Izp = np.atleast_2d((IxxStab * IzzStab - IxzStab**2) / IxxStab).T Ixzp = np.atleast_2d(IxzStab / (IxxStab * IzzStab - IxzStab**2)).T Yv = 0.5 * rho * u0 * S_ref * SSD.CY_beta Yr = 0.25 * rho * u0 * b_ref * S_ref * SSD.CY_r Lv = 0.5 * rho * u0 * b_ref * S_ref * SSD.CL_beta Lp = 0.25 * rho * u0 * b_ref**2 * S_ref * SSD.CL_p Lr = 0.25 * rho * u0 * b_ref**2 * S_ref * SSD.CL_r Nv = 0.5 * rho * u0 * b_ref * S_ref * SSD.CN_beta Np = 0.25 * rho * u0 * b_ref**2 * S_ref * SSD.CN_p Nr = 0.25 * rho * u0 * b_ref**2 * S_ref * SSD.CN_r # Aileron effectiveness # WHat is we don't have ailerons? Will this work? Check this ail = conditions.control_surfaces.aileron.static_stability.coefficients Ya = 0.5 * rho * u0 * u0 * S_ref * ail.Y La = 0.5 * rho * u0 * u0 * S_ref * b_ref * ail.L Na = 0.5 * rho * u0 * u0 * S_ref * b_ref * ail.N BLat[:,0,0] = (Ya / m).T[0] BLat[:,1,0] = (La / Ixp + Ixzp * Na).T[0] BLat[:,2,0] = (Ixzp * La + Na / Izp).T[0] BLat[:,3,0] = 0 ALat[:,0,0] = (Yv / m).T[0] ALat[:,0,2] = (Yr/m - u0).T[0] ALat[:,0,3] = (g * np.cos(theta0)).T[0] ALat[:,1,0] = (Lv / Ixp + Ixzp * Nv).T[0] ALat[:,1,1] = (Lp / Ixp + Ixzp * Np).T[0] ALat[:,1,2] = (Lr / Ixp + Ixzp * Nr).T[0] ALat[:,1,3] = 0 ALat[:,2,0] = (Ixzp * Lv + Nv / Izp).T[0] ALat[:,2,1] = (Ixzp * Lp + Np / Izp).T[0] ALat[:,2,2] = (Ixzp * Lr + Nr / Izp).T[0] ALat[:,2,3] = 0 ALat[:,3,0] = 0 ALat[:,3,1] = 1 ALat[:,3,2] = (np.tan(theta0)).T[0] ALat[:,3,3] = 0 LatModes = np.zeros((n_cpts,4),dtype=complex) dutchRollFreqHz = np.zeros((n_cpts,1)) dutchRollDamping = np.zeros((n_cpts,1)) dutchRollTimeDoubleHalf = np.zeros((n_cpts,1)) rollSubsistenceFreqHz = np.zeros((n_cpts,1)) rollSubsistenceTimeConstant = np.zeros((n_cpts,1)) rollSubsistenceDamping = np.zeros((n_cpts,1)) spiralFreqHz = np.zeros((n_cpts,1)) spiralTimeDoubleHalf = np.zeros((n_cpts,1)) spiralDamping = np.zeros((n_cpts,1)) dutchRoll_mode_real = np.zeros((n_cpts,1)) try: LatModes , V = np.linalg.eig(ALat) # State order: u, w, q, theta # Change to LatModes? If we changed D above then mind as well change this too # LatModes[:,:] = D[:,:] real_parts = LatModes.real unique_elements, counts = np.unique(real_parts, return_counts=True, axis=1) idx = np.where(counts==2)[0] dutchRollFreqHz = abs(LatModes[:,idx]) / (2 * np.pi) dutchRollDamping = np.sqrt(1/ (1 + ( LatModes[:,idx].imag/ LatModes[:,idx].real )**2 )) dutchRollTimeDoubleHalf = np.log(2) / abs(2 * np.pi * dutchRollFreqHz * dutchRollDamping) dutchRoll_mode_real = LatModes[:,idx].real / (2 * np.pi) dutch_roll_idx = np.where( unique_elements[:, idx] != LatModes.real ) remaining_modes = LatModes[dutch_roll_idx].reshape(n_cpts,2) rollInd = np.argmin(remaining_modes,axis=1) rollSubsistenceFreqHz = np.atleast_2d(abs(remaining_modes[Ind,rollInd]) / 2 / np.pi).T rollSubsistenceDamping = np.atleast_2d(- np.sign(remaining_modes[Ind,rollInd].real)).T rollSubsistenceTimeConstant = 1 / (2 * np.pi * rollSubsistenceFreqHz * rollSubsistenceDamping) # Find spiral mode spiralInd = np.argmax(remaining_modes,axis=1) spiralFreqHz = np.atleast_2d(abs(remaining_modes[Ind,spiralInd]) / 2 / np.pi).T spiralDamping = np.atleast_2d(- np.sign(remaining_modes[Ind,spiralInd].real)).T spiralTimeDoubleHalf = np.log(2) / abs(2 * np.pi * spiralFreqHz * spiralDamping) except: pass # Inertial coupling susceptibility # See Etkin & Reid pg. 118 DS.pMax = min(min(np.sqrt(-Mw * u0 / (Izz - Ixx))), min(np.sqrt(-Nv * u0 / (Iyy - Ixx)))) # ----------------------------------------------------------------------------------------------------------------------- # Store Results # ------------------------------------------------------------------------------------------------------------------------ DS.LongModes.LongModes = LonModes DS.LongModes.phugoidFreqHz = phugoidFreqHz DS.LongModes.phugoidDamping = phugoidDamping DS.LongModes.phugoidTimeDoubleHalf = phugoidTimeDoubleHalf DS.LongModes.shortPeriodFreqHz = shortPeriodFreqHz DS.LongModes.shortPeriodDamping = shortPeriodDamping DS.LongModes.shortPeriodTimeDoubleHalf = shortPeriodTimeDoubleHalf DS.LatModes.LatModes = LatModes DS.LatModes.dutchRollFreqHz = dutchRollFreqHz DS.LatModes.dutchRollDamping = dutchRollDamping DS.LatModes.dutchRollTimeDoubleHalf = dutchRollTimeDoubleHalf DS.LatModes.dutchRoll_mode_real = dutchRoll_mode_real DS.LatModes.rollSubsistenceFreqHz = rollSubsistenceFreqHz DS.LatModes.rollSubsistenceTimeConstant = rollSubsistenceTimeConstant DS.LatModes.rollSubsistenceDamping = rollSubsistenceDamping DS.LatModes.spiralFreqHz = spiralFreqHz DS.LatModes.spiralTimeDoubleHalf = spiralTimeDoubleHalf DS.LatModes.spiralDamping = spiralDamping return