def test_softmax(plot=False): # Test softmax x = np.linspace(-10, 10, 100) y1 = x y2 = -2 * x - 3 hardness = 0.5 y_soft = np.softmax(y1, y2, hardness=hardness) assert np.softmax(0, 0, hardness=1) == np.log(2) if plot: import matplotlib.pyplot as plt import seaborn as sns sns.set(font_scale=1) fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8), dpi=200) plt.plot(x, y1, label="y1") plt.plot(x, y2, label="y2") plt.plot(x, y_soft, label="softmax") plt.xlabel(r"x") plt.ylabel(r"y") plt.title(r"Softmax") plt.tight_layout() plt.legend() plt.show()
def Cf_flat_plate(Re_L: float, method="hybrid-sharpe-convex") -> float: """ Returns the mean skin friction coefficient over a flat plate. Don't forget to double it (two sides) if you want a drag coefficient. Args: Re_L: Reynolds number, normalized to the length of the flat plate. method: The method of computing the skin friction coefficient. One of: * "blasius": Uses the Blasius solution. Citing Cengel and Cimbala, "Fluid Mechanics: Fundamentals and Applications", Table 10-4. Valid approximately for Re_L <= 5e5. * "turbulent": Uses turbulent correlations for smooth plates. Citing Cengel and Cimbala, "Fluid Mechanics: Fundamentals and Applications", Table 10-4. Valid approximately for 5e5 <= Re_L <= 1e7. * "hybrid-cengel": Uses turbulent correlations for smooth plates, but accounts for a non-negligible laminar run at the beginning of the plate. Citing Cengel and Cimbala, "Fluid Mechanics: Fundamentals and Applications", Table 10-4. Returns: Mean skin friction coefficient over a flat plate. Valid approximately for 5e5 <= Re_L <= 1e7. * "hybrid-schlichting": Schlichting's model, that roughly accounts for a non-negligtible laminar run. Citing "Boundary Layer Theory" 7th Ed., pg. 644 * "hybrid-sharpe-convex": A hybrid model that blends the Blasius and Schlichting models. Convex in log-log space; however, it may overlook some truly nonconvex behavior near transitional Reynolds numbers. * "hybrid-sharpe-nonconvex": A hybrid model that blends the Blasius and Cengel models. Nonconvex in log-log-space; however, it may capture some truly nonconvex behavior near transitional Reynolds numbers. You can view all of these functions graphically using `aerosandbox.library.aerodynamics.test_aerodynamics.test_Cf_flat_plate.py` """ Re_L = np.abs(Re_L) if method == "blasius": return 1.328 / Re_L**0.5 elif method == "turbulent": return 0.074 / Re_L**(1 / 5) elif method == "hybrid-cengel": return 0.074 / Re_L**(1 / 5) - 1742 / Re_L elif method == "hybrid-schlichting": return 0.02666 * Re_L**-0.139 elif method == "hybrid-sharpe-convex": return np.softmax(Cf_flat_plate(Re_L, method="blasius"), Cf_flat_plate(Re_L, method="hybrid-schlichting"), hardness=1e3) elif method == "hybrid-sharpe-nonconvex": return np.softmax(Cf_flat_plate(Re_L, method="blasius"), Cf_flat_plate(Re_L, method="hybrid-cengel"), hardness=1e3)
def mass_hpa_propeller( diameter, max_power, include_variable_pitch_mechanism=False ): """ Returns the estimated mass of a propeller assembly for low-disc-loading applications (human powered airplane, paramotor, etc.) :param diameter: diameter of the propeller [m] :param max_power: maximum power of the propeller [W] :param include_variable_pitch_mechanism: boolean, does this propeller have a variable pitch mechanism? :return: estimated weight [kg] """ mass_propeller = ( 0.495 * (diameter / 1.25) ** 1.6 * np.softmax(0.6, max_power / 14914, hardness=5) ** 2 ) # Baselining to a 125cm E-Props Top 80 Propeller for paramotor, with some sketchy scaling assumptions # Parameters on diameter exponent and min power were chosen such that Daedalus propeller is roughly on the curve. mass_variable_pitch_mech = 216.8 / 800 * mass_propeller # correlation to Daedalus data: http://journals.sfu.ca/ts/index.php/ts/article/viewFile/760/718 if include_variable_pitch_mechanism: mass_propeller += mass_variable_pitch_mech return mass_propeller
def separation_parameter(alpha, Re=0): """ Positive if separated, negative if attached. This will be an input to a tanh() sigmoid blend via asb.numpy.blend(), so a value of 1 means the flow is ~90% separated, and a value of -1 means the flow is ~90% attached. """ return 0.5 * np.softmax(alpha - alpha_stall_positive, alpha_stall_negative - alpha)
def underlying_function(x): # Softmax of three linear functions def f1(x): return -3 * x - 4 def f2(x): return -0.25 * x + 1 def f3(x): return 2 * x - 12 return np.softmax(f1(x), f2(x), f3(x), hardness=1)
def induced_drag_ratio_from_ground_effect(h_over_b # type: float ): """ Gives the ratio of actual induced drag to free-flight induced drag experienced by a wing in ground effect. Artificially smoothed below around h/b == 0.05 to retain differentiability and practicality. Source: W. F. Phillips, D. F. Hunsaker, "Lifting-Line Predictions for Induced Drag and Lift in Ground Effect". Using Equation 5 from the paper, which is modified from a model from Torenbeek: Torenbeek, E. "Ground Effects", 1982. :param h_over_b: (Height above ground) divided by (wingspan). :return: Ratio of induced drag in ground effect to induced drag out of ground effect [unitless] """ h_over_b = np.softmax(h_over_b, 0, hardness=1 / 0.03) return 1 - np.exp(-4.01 * (2 * h_over_b)**0.717)
def Cd_wave_Korn(Cl, t_over_c, mach, sweep=0, kappa_A=0.95): """ Wave drag_force coefficient prediction using the low-fidelity Korn Equation method; derived in "Configuration Aerodynamics" by W.H. Mason, Sect. 7.5.2, pg. 7-18 :param Cl: Sectional lift coefficient :param t_over_c: thickness-to-chord ratio :param sweep: sweep angle, in degrees :param kappa_A: Airfoil technology factor (0.95 for supercritical section, 0.87 for NACA 6-series) :return: Wave drag coefficient """ smooth_abs_Cl = np.softmax(Cl, -Cl, hardness=10) mach = np.fmax(mach, 0) Mdd = kappa_A / np.cosd(sweep) - t_over_c / np.cosd( sweep)**2 - smooth_abs_Cl / (10 * np.cosd(sweep)**3) Mcrit = Mdd - (0.1 / 80)**(1 / 3) Cd_wave = np.where(mach > Mcrit, 20 * (mach - Mcrit)**4, 0) return Cd_wave
def mach_crit_Korn( CL, t_over_c, sweep=0, kappa_A=0.95 ): """ Wave drag_force coefficient prediction using the low-fidelity Korn Equation method; derived in "Configuration Aerodynamics" by W.H. Mason, Sect. 7.5.2, pg. 7-18 Args: CL: Sectional lift coefficient t_over_c: thickness-to-chord ratio sweep: sweep angle, in degrees kappa_A: Airfoil technology factor (0.95 for supercritical section, 0.87 for NACA 6-series) Returns: """ smooth_abs_CL = np.softmax(CL, -CL, hardness=10) M_dd = kappa_A / np.cosd(sweep) - t_over_c / np.cosd(sweep) ** 2 - smooth_abs_CL / (10 * np.cosd(sweep) ** 3) M_crit = M_dd - (0.1 / 80) ** (1 / 3) return M_crit
def approximate_CD_wave( mach, mach_crit, CD_wave_at_fully_supersonic, ): """ An approximate relation for computing transonic wave drag, based on an object's Mach number. Considered reasonably valid from Mach 0 up to around Mach 2 or 3-ish. Methodology is a combination of: * The methodology described in Raymer, "Aircraft Design: A Conceptual Approach", Section 12.5.10 Transonic Parasite Drag (pg. 449 in Ed. 2) and * The methodology described in W.H. Mason's Configuration Aerodynamics, Chapter 7. Transonic Aerodynamics of Airfoils and Wings. Args: mach: Mach number at the operating point to be evaluated mach_crit: Critical mach number, a function of the body geometry CD_wave_at_fully_supersonic: The wave drag coefficient of the body at the speed that it first goes ( effectively) fully supersonic. Here, that is taken to mean at the Mach 1.2 case. This value should probably be derived using something similar to a Sears-Haack relation for the body in question, with a markup depending on geometry smoothness. The CD_wave predicted by this function will match this value exactly at M=1.2 and M=1.05. The peak CD_wave that is predicted is ~1.23 * this value, which occurs at M=1.10. In the high-Mach limit, this function asymptotes at 0.80 * this value, as empirically stated by Raymer. However, this model is only approximate and is likely not valid for high-supersonic flows. Returns: The approximate wave drag coefficient at the specified Mach number. The reference area is whatever the reference area used in the `CD_wave_at_fully_supersonic` parameter is. """ mach_crit_max = 1 - (0.1 / 80)**(1 / 3) mach_crit = -np.softmax(-mach_crit, -mach_crit_max, hardness=50) ### The following approximate relation is derived in W.H. Mason, "Configuration Aerodynamics", Chapter 7. Transonic Aerodynamics of Airfoils and Wings. ### Equation 7-8 on Page 7-19. ### This is in turn based on Lock's proposed empirically-derived shape of the drag rise, from Hilton, W.F., High Speed Aerodynamics, Longmans, Green & Co., London, 1952, pp. 47-49 mach_dd = mach_crit + (0.1 / 80)**(1 / 3) ### Model drag sections and cutoffs: return CD_wave_at_fully_supersonic * np.where( mach < mach_crit, 0, np.where( mach < mach_dd, 20 * (mach - mach_crit)**4, np.where( mach < 1.05, cubic_hermite_patch(mach, x_a=mach_dd, x_b=1.05, f_a=20 * (0.1 / 80)**(4 / 3), f_b=1, dfdx_a=0.1, dfdx_b=10), np.where(mach < 1.2, cubic_hermite_patch(mach, x_a=1.05, x_b=1.2, f_a=1, f_b=1, dfdx_a=10, dfdx_b=-4), np.blend( switch=4 * 2 * (mach - 1.2) / (1.2 - 0.8), value_switch_high=0.8, value_switch_low=1.2, ) # 0.8 + 0.2 * np.exp(20 * (1.2 - mach)) ))))
def CL_over_Cl( aspect_ratio: float, mach: float = 0., sweep: float = 0., Cl_is_compressible: bool = True ) -> float: """ Returns the ratio of 3D lift coefficient (with compressibility) to the 2D lift coefficient. Specifically: CL_3D / CL_2D Args: aspect_ratio: The aspect ratio of the wing. mach: The freestream Mach number. sweep: The sweep of the wing, in degrees. To be most accurate, this should be the sweep at the locus of thickest points along the wing. Cl_is_compressible: This flag indicates whether the 2D airfoil data already has compressibility effects modeled. For example: * If this flag is True, this function returns: CL_3D / CL_2D, where CL_2D is the sectional lift coefficient based on the local profile at the freestream mach number. * If this flag is False, this function returns: CL_3D / CL_2D_at_mach_zero, where CL_2D_... is the sectional lift coefficient based on the local profile at mach zero. For most accurate results, set this flag to True, and then model profile characteristics separately. """ prandtl_glauert_beta_squared_ideal = 1 - mach ** 2 # beta_squared = 1 - mach ** 2 beta_squared = np.softmax( prandtl_glauert_beta_squared_ideal, -prandtl_glauert_beta_squared_ideal, hardness=3.0 ) ### Alternate formulations # CL_ratio = aspect_ratio / (aspect_ratio + 2) # Equivalent to equation in Drela's FVA in incompressible, 2*pi*alpha limit. # CL_ratio = aspect_ratio / (2 + np.sqrt(4 + aspect_ratio ** 2)) # more theoretically sound at low aspect_ratio ### Formulation from Raymer, Sect. 12.4.1; citing DATCOM. # Comparison to experiment suggests this is the most accurate. # Symbolically simplified to remove the PG singularity. eta = 0.95 CL_ratio = aspect_ratio / ( 2 + ( 4 + (aspect_ratio ** 2 * beta_squared / eta ** 2) + (np.tand(sweep) * aspect_ratio / eta) ** 2 ) ** 0.5 ) if Cl_is_compressible: CL_ratio = CL_ratio * beta_squared ** 0.5 return CL_ratio