def test_fuselage_aerodynamics_optimization(): opti = asb.Opti() alpha = opti.variable(init_guess=0, lower_bound=0, upper_bound=30) beta = opti.variable(init_guess=0) fuselage = asb.Fuselage(xsecs=[ asb.FuselageXSec(xyz_c=[xi, 0, 0], radius=asb.Airfoil("naca0010").local_thickness( 0.8 * xi)) for xi in np.cosspace(0, 1, 20) ], ) aero = asb.AeroBuildup(airplane=asb.Airplane(fuselages=[fuselage]), op_point=asb.OperatingPoint(velocity=10, alpha=alpha, beta=beta)).run() opti.minimize(-aero["L"] / aero["D"]) sol = opti.solve(verbose=False) print(sol.value(alpha)) assert sol.value(alpha) > 10 and sol.value(alpha) < 20 assert sol.value(beta) == pytest.approx(0, abs=1e-3) opti.minimize(aero["D"]) sol = opti.solve(verbose=False) assert sol.value(alpha) == pytest.approx(0, abs=1e-2) assert sol.value(beta) == pytest.approx(0, abs=1e-2)
xsecs=[ asb.WingXSec( xyz_le=[0, 0, 0], chord=0.7, ), asb.WingXSec(xyz_le=[0.14, 1.25, 0], chord=0.42), ]), asb.Wing(name="V-stab", xyz_le=[4, 0, 0], xsecs=[ asb.WingXSec( xyz_le=[0, 0, 0], chord=0.7, ), asb.WingXSec(xyz_le=[0.14, 0, 1], chord=0.42) ]) ], fuselages=[ asb.Fuselage(name="Fuselage", xyz_le=[0, 0, 0], xsecs=[ asb.FuselageXSec( xyz_c=[xi * 5 - 0.5, 0, 0], radius=asb.Airfoil("naca0024").local_thickness( x_over_c=xi)) for xi in np.cosspace(0, 1, 30) ]) ]) if __name__ == '__main__': airplane.draw()
def repanel( self, n_points_per_side: int = 100, ) -> 'Airfoil': """ Returns a repaneled version of the airfoil with cosine-spaced coordinates on the upper and lower surfaces. :param n_points_per_side: Number of points per side (upper and lower) of the airfoil [int] Notes: The number of points defining the final airfoil will be n_points_per_side*2-1, since one point (the leading edge point) is shared by both the upper and lower surfaces. :return: Returns the new airfoil. """ upper_original_coors = self.upper_coordinates( ) # Note: includes leading edge point, be careful about duplicates lower_original_coors = self.lower_coordinates( ) # Note: includes leading edge point, be careful about duplicates # Find distances between coordinates, assuming linear interpolation upper_distances_between_points = ( (upper_original_coors[:-1, 0] - upper_original_coors[1:, 0])**2 + (upper_original_coors[:-1, 1] - upper_original_coors[1:, 1])** 2)**0.5 lower_distances_between_points = ( (lower_original_coors[:-1, 0] - lower_original_coors[1:, 0])**2 + (lower_original_coors[:-1, 1] - lower_original_coors[1:, 1])** 2)**0.5 upper_distances_from_TE = np.hstack( (0, np.cumsum(upper_distances_between_points))) lower_distances_from_LE = np.hstack( (0, np.cumsum(lower_distances_between_points))) upper_distances_from_TE_normalized = upper_distances_from_TE / upper_distances_from_TE[ -1] lower_distances_from_LE_normalized = lower_distances_from_LE / lower_distances_from_LE[ -1] distances_from_TE_normalized = np.hstack( (upper_distances_from_TE_normalized, 1 + lower_distances_from_LE_normalized[1:])) # Generate a cosine-spaced list of points from 0 to 1 cosspaced_points = np.cosspace(0, 1, n_points_per_side) s = np.hstack(( cosspaced_points, 1 + cosspaced_points[1:], )) # Check that there are no duplicate points in the airfoil. if np.any(np.diff(distances_from_TE_normalized) == 0): raise ValueError( "This airfoil has a duplicated point (i.e. two adjacent points with the same (x, y) coordinates), so you can't repanel it!" ) x = interp1d( distances_from_TE_normalized, self.x(), kind="cubic", )(s) y = interp1d( distances_from_TE_normalized, self.y(), kind="cubic", )(s) return Airfoil(name=self.name, coordinates=stack_coordinates(x, y))
twist=0, airfoil=tail_airfoil, control_surface_is_symmetric=True, # Rudder control_surface_deflection=0, ), asb.WingXSec( xyz_le=[0.04, 0, 0.15], chord=0.06, twist=0, airfoil=tail_airfoil ) ] ).translate([0.6, 0, 0.07]) ], fuselages=[ asb.Fuselage( name="Fuselage", xsecs=[ asb.FuselageXSec( xyz_c=[0.8 * xi - 0.1, 0, 0.1 * xi - 0.03], radius=0.6 * asb.Airfoil("dae51").local_thickness(x_over_c=xi) ) for xi in np.cosspace(0, 1, 30) ] ) ] ) if __name__ == '__main__': airplane.draw()
def get_kulfan_coordinates( lower_weights=-0.2 * np.ones(5), # type: np.ndarray upper_weights=0.2 * np.ones(5), # type: np.ndarray enforce_continuous_LE_radius=True, TE_thickness=0., # type: float n_points_per_side=_default_n_points_per_side, # type: int N1=0.5, # type: float N2=1.0, # type: float ) -> np.ndarray: """ Calculates the coordinates of a Kulfan (CST) airfoil. To make a Kulfan (CST) airfoil, use the following syntax: asb.Airfoil("My Airfoil Name", coordinates = asb.kulfan_coordinates(*args)) More on Kulfan (CST) airfoils: http://brendakulfan.com/docs/CST2.pdf Notes on N1, N2 (shape factor) combinations: * 0.5, 1: Conventional airfoil * 0.5, 0.5: Elliptic airfoil * 1, 1: Biconvex airfoil * 0.75, 0.75: Sears-Haack body (radius distribution) * 0.75, 0.25: Low-drag projectile * 1, 0.001: Cone or wedge airfoil * 0.001, 0.001: Rectangle, circular duct, or circular rod. :param lower_weights: :param upper_weights: :param enforce_continuous_LE_radius: Enforces a continous leading-edge radius by throwing out the first lower weight. :param TE_thickness: :param n_points_per_side: :param N1: LE shape factor :param N2: TE shape factor :return: """ if enforce_continuous_LE_radius: lower_weights[0] = -1 * upper_weights[0] x_lower = np.cosspace(0, 1, n_points_per_side) x_upper = x_lower[::-1] x_lower = x_lower[ 1:] # Trim off the nose coordinate so there are no duplicates def shape(w, x): # Class function C = x**N1 * (1 - x)**N2 # Shape function (Bernstein polynomials) n = len(w) - 1 # Order of Bernstein polynomials K = comb(n, np.arange(n + 1)) # Bernstein polynomial coefficients S_matrix = (w * K * np.expand_dims(x, 1)**np.arange(n + 1) * np.expand_dims(1 - x, 1)**(n - np.arange(n + 1)) ) # Polynomial coefficient * weight matrix # S = np.sum(S_matrix, axis=1) S = np.array( [np.sum(S_matrix[i, :]) for i in range(S_matrix.shape[0])]) # Calculate y output y = C * S return y y_lower = shape(lower_weights, x_lower) y_upper = shape(upper_weights, x_upper) # TE thickness y_lower -= x_lower * TE_thickness / 2 y_upper += x_upper * TE_thickness / 2 x = np.concatenate([x_upper, x_lower]) y = np.concatenate([y_upper, y_lower]) coordinates = np.vstack((x, y)).T return coordinates
def get_NACA_coordinates( name: str = 'naca2412', n_points_per_side: int = _default_n_points_per_side) -> np.ndarray: """ Returns the coordinates of a specified 4-digit NACA airfoil. Args: name: Name of the NACA airfoil. n_points_per_side: Number of points per side of the airfoil (top/bottom). Returns: The coordinates of the airfoil as a Nx2 ndarray [x, y] """ name = name.lower().strip() if not "naca" in name: raise ValueError("Not a NACA airfoil!") nacanumber = name.split("naca")[1] if not nacanumber.isdigit(): raise ValueError("Couldn't parse the number of the NACA airfoil!") if not len(nacanumber) == 4: raise NotImplementedError( "Only 4-digit NACA airfoils are currently supported!") # Parse max_camber = int(nacanumber[0]) * 0.01 camber_loc = int(nacanumber[1]) * 0.1 thickness = int(nacanumber[2:]) * 0.01 # Referencing https://en.wikipedia.org/wiki/NACA_airfoil#Equation_for_a_cambered_4-digit_NACA_airfoil # from here on out # Make uncambered coordinates x_t = np.cosspace(0, 1, n_points_per_side) # Generate some cosine-spaced points y_t = 5 * thickness * ( +0.2969 * x_t**0.5 - 0.1260 * x_t - 0.3516 * x_t**2 + 0.2843 * x_t**3 - 0.1015 * x_t**4 # 0.1015 is original, #0.1036 for sharp TE ) if camber_loc == 0: camber_loc = 0.5 # prevents divide by zero errors for things like naca0012's. # Get camber y_c = np.where( x_t <= camber_loc, max_camber / camber_loc**2 * (2 * camber_loc * x_t - x_t**2), max_camber / (1 - camber_loc)**2 * ((1 - 2 * camber_loc) + 2 * camber_loc * x_t - x_t**2)) # Get camber slope dycdx = np.where(x_t <= camber_loc, 2 * max_camber / camber_loc**2 * (camber_loc - x_t), 2 * max_camber / (1 - camber_loc)**2 * (camber_loc - x_t)) theta = np.arctan(dycdx) # Combine everything x_U = x_t - y_t * np.sin(theta) x_L = x_t + y_t * np.sin(theta) y_U = y_c + y_t * np.cos(theta) y_L = y_c - y_t * np.cos(theta) # Flip upper surface so it's back to front x_U, y_U = x_U[::-1], y_U[::-1] # Trim 1 point from lower surface so there's no overlap x_L, y_L = x_L[1:], y_L[1:] x = np.hstack((x_U, x_L)) y = np.hstack((y_U, y_L)) return stack_coordinates(x, y)
import aerosandbox as asb import aerosandbox.numpy as np n_timesteps = 301 mass_block = 1 opti = asb.Opti() time = np.cosspace(0, 1, n_timesteps) position = opti.variable(init_guess=np.linspace(0, 1, n_timesteps)) velocity = opti.derivative_of( position, with_respect_to=time, derivative_init_guess=1, ) force = opti.variable(init_guess=np.linspace(1, -1, n_timesteps), n_vars=n_timesteps) opti.constrain_derivative( variable=velocity, with_respect_to=time, derivative=force / mass_block, ) effort_expended = np.sum(np.trapz(force**2) * np.diff(time)) opti.minimize(effort_expended)
def test_spacing(types): for x in types["scalar"]: np.linspace(x - 1, x + 1, 10) np.cosspace(x - 1, x + 1, 10)
def tangent( x_over_L: np.ndarray, ): return haack_series( x_over_L=x_over_L, C=2 / 3 ) if __name__ == '__main__': import matplotlib.pyplot as plt import aerosandbox.tools.pretty_plots as p fig, ax = plt.subplots() x_over_L = np.cosspace(0, 1, 2000) FR = 3 plt.plot(x_over_L * FR, karman(x_over_L), label="$C=0$ (LD-Haack, Karman)") plt.plot(x_over_L * FR, LV_haack(x_over_L), label="$C=1/3$ (LV-Haack)") plt.plot(x_over_L * FR, tangent(x_over_L), label="$C=2/3$ (Tangent)") p.equal() p.show_plot( f"Nosecone Haack Series\nFineness Ratio $FR = {FR}$", "Length $x$", "Radius $r$" )
import aerosandbox as asb import aerosandbox.numpy as np import matplotlib.pyplot as plt import aerosandbox.tools.pretty_plots as p fuselage = asb.Fuselage(xsecs=[ asb.FuselageXSec(xyz_c=[xi, 0, 0], radius=asb.Airfoil("naca0020").local_thickness(xi) / 2) for xi in np.cosspace(0, 1, 20) ], ) fig, ax = plt.subplots(figsize=(7, 6)) V = np.linspace(10, 1000, 1001) op_point = asb.OperatingPoint(velocity=V, ) aero = asb.AeroBuildup( airplane=asb.Airplane(fuselages=[fuselage]), op_point=op_point, ).run() plt.plot(op_point.mach(), aero["CD"], label="Full Model") aero = asb.AeroBuildup(airplane=asb.Airplane(fuselages=[fuselage]), op_point=op_point, include_wave_drag=False).run() plt.plot(op_point.mach(), aero["CD"], zorder=1.9, label="Model without Wave Drag")