def test_basic_math(types): for x in types["all"]: for y in types["all"]: ### Arithmetic x + y x - y x * y x / y np.sum(x) # Sum of all entries of array-like object x ### Exponentials & Powers x**y np.power(x, y) np.exp(x) np.log(x) np.log10(x) np.sqrt(x) # Note: do x ** 0.5 rather than np.sqrt(x). ### Trig np.sin(x) np.cos(x) np.tan(x) np.arcsin(x) np.arccos(x) np.arctan(x) np.arctan2(y, x) np.sinh(x) np.cosh(x) np.tanh(x) np.arcsinh(x) np.arccosh(x) np.arctanh(x - 0.5) # `- 0.5` to give valid argument
def haack_series( x_over_L: np.ndarray, C=1 / 3 ): theta = np.arccos(1 - 2 * x_over_L) radius = (( theta - np.sin(2 * theta) / 2 + C * np.sin(theta) ** 3 ) / np.pi) ** 0.5 return radius
def rotation_matrix_from_euler_angles(roll_angle: Union[float, _onp.ndarray] = 0, pitch_angle: Union[float, _onp.ndarray] = 0, yaw_angle: Union[float, _onp.ndarray] = 0, as_array: bool = True): """ Yields the rotation matrix that corresponds to a given Euler angle rotation. Note: This uses the standard (yaw, pitch, roll) Euler angle rotation, where: * First, a rotation about x is applied (roll) * Second, a rotation about y is applied (pitch) * Third, a rotation about z is applied (yaw) In other words: R = R_z(yaw) @ R_y(pitch) @ R_x(roll). Note: To use this, pre-multiply your vector to go from body axes to earth axes. Example: >>> vector_earth = rotation_matrix_from_euler_angles(np.pi / 4, np.pi / 4, np.pi / 4) @ vector_body See notes: http://planning.cs.uiuc.edu/node102.html Args: roll_angle: The roll angle, which is a rotation about the x-axis. [radians] pitch_angle: The pitch angle, which is a rotation about the y-axis. [radians] yaw_angle: The yaw angle, which is a rotation about the z-axis. [radians] as_array: Returns: """ sa = sin(yaw_angle) ca = cos(yaw_angle) sb = sin(pitch_angle) cb = cos(pitch_angle) sc = sin(roll_angle) cc = cos(roll_angle) rot = [[ca * cb, ca * sb * sc - sa * cc, ca * sb * cc + sa * sc], [sa * cb, sa * sb * sc + ca * cc, sa * sb * cc - ca * sc], [-sb, cb * sc, cb * cc]] if as_array: return array(rot) else: return rot
def test_fit_model_weighting(): x = np.linspace(0, 10) y = np.sin(x) fm = FittedModel( model=lambda x, p: p["m"] * x + p["b"], x_data=x, y_data=y, parameter_guesses={ "m": 0, "b": 0, }, weights=None ) # Fit a model with no weighting assert fm(10) != pytest.approx(5, abs=1) # Doesn't give a high value at x = 10 fm = FittedModel( model=lambda x, p: p["m"] * x + p["b"], x_data=x, y_data=y, parameter_guesses={ "m": 0, "b": 0, }, weights=(x > 0) & (x < 2) ) # Fit a model with weighting assert fm(10) == pytest.approx(5, abs=1) # Gives a high value at x = 10 fm.plot()
def make_matrix(x): matrix = np.ones((len(x), degree + 1)) for j in range(1, degree - 2): matrix[:, j] = matrix[:, j - 1] * x matrix[:, degree - 2] = np.cos(x) matrix[:, degree - 1] = np.sin(x) matrix[:, degree] = np.exp(x) return matrix
def derivatives(t, y): this_dyn = dyn.get_new_instance_with_state(y) if gravity: this_dyn.add_gravity_force() q = 0.5 * 1.225 * this_dyn.speed**2 if drag: this_dyn.add_force(Fx=-1 * (0.1)**2 * q, axes="wind") if sideforce: strouhal = 0.2 frequency = 5 # strouhal * this_dyn.speed / 0.1 this_dyn.add_force(Fy=q * 1 * (0.1)**2 * np.sin(2 * np.pi * frequency * t), axes="wind") return this_dyn.unpack_state(this_dyn.state_derivatives())
def rotation_matrix_2D( angle, as_array: bool = True, ): """ Gives the 2D rotation matrix associated with a counterclockwise rotation about an angle. Args: angle: Angle by which to rotate. Given in radians. as_array: Determines whether to return an array-like or just a simple list of lists. Returns: The 2D rotation matrix """ s = sin(angle) c = cos(angle) rot = [[c, -s], [s, c]] if as_array: return array(rot) else: return rot
def airfoil_CDp(alpha, Re, Ma, Cl): alpha_rad = alpha * pi / 180 Re_exp = -0.7 Re_ref = 70000 cd_0 = 0.028 cd_2 = 0.05 #cd_2 = 0.05 cl_cd_0 = 0.5 cl_0 = 0.5 cl_alpha = 5.8 cl_min = -0.3 cl_max = 1.2 # cd = (cd_0 + cd_2 * (cl - cl_cd_0) ** 2) * (Re / Re_ref) ** Re_exp cd = (cd_0 + cd_2 * (Cl - cl_cd_0) ** 2) * (Re / Re_ref) ** Re_exp aCD0 = (cl_cd_0 - cl_0) / cl_alpha dcd_stall = 2 * (np.sin(alpha - aCD0)) ** 2 if cas.is_equal(Cl, cl_max): cd = dcd_stall + cd if cas.is_equal(Cl, cl_min): cd = dcd_stall + cd return cd
def state_derivatives(self) -> Dict[str, Union[float, np.ndarray]]: d_speed = self.Fx_w / self.mass_props.mass sb = np.sin(self.bank) cb = np.cos(self.bank) force_gamma_direction = -cb * self.Fz_w - sb * self.Fy_w # Force in the direction that acts to increase gamma force_track_direction = -sb * self.Fz_w + cb * self.Fy_w # Force in the direction that acts to increase track d_gamma = force_gamma_direction / self.mass_props.mass / self.speed d_track = force_track_direction / self.mass_props.mass / self.speed / np.cos( self.gamma) return { "x_e": self.u_e, "y_e": self.v_e, "z_e": self.w_e, "speed": d_speed, "gamma": d_gamma, "track": d_track, }
def sine_squared_gust(reduced_time: float) -> float: """ A canonical gust of used by the FAA to show 'compliance with the requirements of Title 14, Code of Federal Regulations (14 CFR) 25.341, Gust and turbulence loads. Section 25.341 specifies the discrete gust and continuous turbulence dynamic load conditions that apply to the airplane and engines.' Args: reduced_time (float) Returns: gust_velocity (float) """ gust_strength = 1 start = 5 finish = 10 gust_width_to_chord_ratio = 5 if start <= reduced_time <= finish: gust_velocity = (gust_strength * np.sin( (np.pi * reduced_time) / gust_width_to_chord_ratio)**2) else: gust_velocity = 0 return gust_velocity
np.sum([da_ds[j] * wagner[i - j] * ds[j] for j in range(i)])) return lift_coefficient if __name__ == "__main__": n = 1000 n1 = int(n / 3) n2 = int(2 * n / 3) time = np.linspace(0, 100, n) velocity = 0.2 chord = 1 reduced_time = calculate_reduced_time(time, velocity, chord) gust_velocity = np.zeros_like(reduced_time) gust_velocity[n1:n2] = velocity angle_of_attack = 20 * np.deg2rad(np.sin(reduced_time)) #angle_of_attack[n1:n2] = np.deg2rad(-20) cl_k = duhamel_integral_kussner(reduced_time, gust_velocity, velocity) cl_w = duhamel_integral_wagner(reduced_time, angle_of_attack) plt.figure(dpi=300) plt.plot(reduced_time, cl_w, label="wagner") plt.plot(reduced_time, cl_k, label="kussner") plt.plot(reduced_time, cl_k + cl_w, label="total") plt.xlabel("Reduced time, t*") plt.ylabel("$C_\ell$") plt.legend()
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)
def equations_of_motion( xe=0, ye=0, ze=0, u=0, v=0, w=0, phi=0, theta=0, psi=0, p=0, q=0, r=0, X=0, Y=0, Z=0, L=0, M=0, N=0, mass=1, Ixx=1, Iyy=1, Izz=1, Ixy=0, Iyz=0, Ixz=0, g=9.81, hx=0, hy=0, hz=0, ): """ Computes the state derivatives (i.e. equations of motion) for a body in 3D space. Based on Section 9.8.2 of Flight Vehicle Aerodynamics by Mark Drela. Args: xe: x-position, in Earth axes. [meters] ye: y-position, in Earth axes. [meters] ze: z-position, in Earth axes. [meters] u: x-velocity, in body axes. [m/s] v: y-velocity, in body axes. [m/s] w: z-velocity, in body axes. [m/s] phi: roll angle. Uses yaw-pitch-roll Euler angle convention. [rad] theta: pitch angle. Uses yaw-pitch-roll Euler angle convention. [rad] psi: yaw angle. Uses yaw-pitch-roll Euler angle convention. [rad] p: x-angular-velocity, in body axes. [rad/sec] q: y-angular-velocity, in body axes. [rad/sec] r: z-angular-velocity, in body axes. [rad/sec] X: x-direction force, in body axes. [N] Y: y-direction force, in body axes. [N] Z: z-direction force, in body axes. [N] L: Moment about the x axis, in body axes. Assumed these moments are applied about the center of mass. [Nm] M: Moment about the y axis, in body axes. Assumed these moments are applied about the center of mass. [Nm] N: Moment about the z axis, in body axes. Assumed these moments are applied about the center of mass. [Nm] mass: Mass of the body. [kg] Ixx: Respective component of the (symmetric) moment of inertia tensor. Iyy: Respective component of the (symmetric) moment of inertia tensor. Izz: Respective component of the (symmetric) moment of inertia tensor. Ixy: Respective component of the (symmetric) moment of inertia tensor. Ixz: Respective component of the (symmetric) moment of inertia tensor. Iyz: Respective component of the (symmetric) moment of inertia tensor. g: Magnitude of gravitational acceleration. Assumed to act in the positive-z-in-earth-axes ("downward") direction. [m/s^2] hx: x-component of onboard angular momentum (e.g. propellers), in body axes. [kg*m^2/sec] hy: y-component of onboard angular momentum (e.g. propellers), in body axes. [kg*m^2/sec] hz: z-component of onboard angular momentum (e.g. propellers), in body axes. [kg*m^2/sec] Returns: Time derivatives of each of the 12 state variables, given in the following order: d_xe, d_ye, d_ze, d_u, d_v, d_w, d_phi, d_theta, d_psi, d_p, d_q, d_r, d_X, d_Y, d_Z, d_L, d_M, d_N, """ ### Trig Shorthands sphi = np.sin(phi) cphi = np.cos(phi) sthe = np.sin(theta) cthe = np.cos(theta) spsi = np.sin(psi) cpsi = np.cos(psi) ##### Equations of Motion ### Position derivatives d_xe = ((cthe * cpsi) * u + (sphi * sthe * cpsi - cphi * spsi) * v + (cphi * sthe * cpsi + sphi * spsi) * w) d_ye = ((cthe * sphi) * u + (sphi * sthe * spsi + cphi * cpsi) * v + (cphi * sthe * spsi - sphi * cpsi) * w) d_ze = ((-sthe) * u + (sphi * cthe) * v + (cphi * cthe) * w) ### Velocity derivatives d_u = ((X / mass - g * sthe) - q * w + r * v) d_v = ((Y / mass + g * sphi * cthe) - r * u + p * w) d_w = ((Z / mass + g * cphi * cthe) - p * v + q * u) ### Angle derivatives d_phi = (p + q * sphi * sthe / cthe + r * cphi * sthe / cthe) d_theta = (q * cphi - r * sphi) d_psi = (q * sphi / cthe + r * cphi / cthe) ### Angular velocity derivatives RHS_L = (L - (Izz - Iyy) * q * r - Iyz * (q**2 - r**2) - Ixz * p * q + Ixy * p * r - hz * q + hy * r) RHS_M = (M - (Ixx - Izz) * r * p - Ixz * (r**2 - p**2) - Ixy * q * r + Iyz * q * p - hx * r + hz * p) RHS_N = (N - (Iyy - Ixx) * p * q - Ixy * (p**2 - q**2) - Iyz * r * p + Ixz * r * q - hy * p + hx * q) i11, i22, i33, i12, i23, i13 = inv_symmetric_3x3(Ixx, Iyy, Izz, Ixy, Iyz, Ixz) d_p = i11 * RHS_L + i12 * RHS_M + i13 * RHS_N d_q = i12 * RHS_L + i22 * RHS_M + i23 * RHS_N d_r = i13 * RHS_L + i23 * RHS_M + i33 * RHS_N return d_xe, d_ye, d_ze, d_u, d_v, d_w, d_phi, d_theta, d_psi, d_p, d_q, d_r
def w_e(self): return -self.speed * np.sin(self.gamma)
def test_invertability_of_diff_trapz(): a = np.sin(np.arange(10)) assert np.all(np.trapz(np.diff(a)) == pytest.approx(np.diff(np.trapz(a))))
def performance( self, speed, throttle_state=1, grade=0, headwind=0, ): ##### Figure out electric thrust force wheel_radius = self.wheel_diameter / 2 wheel_rads_per_sec = speed / wheel_radius wheel_rpm = wheel_rads_per_sec * 30 / np.pi motor_rpm = wheel_rpm / self.gear_ratio ### Limit performance by either max voltage or max current perf_via_max_voltage = self.motor.performance( voltage=self.max_voltage, rpm=motor_rpm, ) perf_via_throttle = self.motor.performance( current=self.max_current * throttle_state, rpm=motor_rpm, ) if perf_via_max_voltage['torque'] > perf_via_throttle['torque']: perf = perf_via_throttle else: perf = perf_via_max_voltage motor_torque = perf['torque'] wheel_torque = motor_torque / self.gear_ratio wheel_force = wheel_torque / wheel_radius thrust = wheel_force ##### Gravity gravity_drag = 9.81 * np.sin(np.arctan(grade)) * self.mass ##### Rolling Resistance # Crr = 0.0020 # Concrete Crr = 0.0050 # Asphalt # Crr = 0.0060 # Gravel # Crr = 0.0070 # Grass # Crr = 0.0200 # Off-road # Crr = 0.0300 # Sand rolling_drag = 9.81 * np.cos(np.arctan(grade)) * self.mass * Crr ##### Aerodynamics # CDA = 0.408 # Tops CDA = 0.324 # Hoods # CDA = 0.307 # Drops # CDA = 0.2914 # Aerobars eff_speed = speed + headwind air_drag = 0.5 * atmo.density() * eff_speed * np.abs(eff_speed) * CDA ##### Summation net_force = thrust - gravity_drag - rolling_drag - air_drag net_accel = net_force / self.mass return { "net acceleration": net_accel, "motor state": perf, }
def v_e(self): return self.speed * np.cos(self.gamma) * np.sin(self.track)
def f(x): return np.sin(x) + 2 * x + 3
def sin(x): return np.sin(x)
def get_trajectory( parameterization: type = asb.DynamicsPointMass3DCartesian, gravity=True, drag=True, sideforce=True, plot=False, verbose=False, ): opti = asb.Opti() if parameterization is asb.DynamicsPointMass3DCartesian: dyn = parameterization( mass_props=asb.MassProperties(mass=1), x_e=opti.variable(np.linspace(0, 300, N)), y_e=opti.variable(np.linspace(0, 0, N), scale=1), z_e=opti.variable(np.linspace(0, 0, N), scale=100), u_e=opti.variable(np.linspace(100, 50, N)), v_e=opti.variable(np.linspace(0, 0, N), scale=1), w_e=opti.variable(np.linspace(-100, 50, N)), ) elif parameterization is asb.DynamicsPointMass3DSpeedGammaTrack: dyn = parameterization( mass_props=asb.MassProperties(mass=1), x_e=opti.variable(np.linspace(0, 300, N)), y_e=opti.variable(np.linspace(0, 0, N), scale=1), z_e=opti.variable(np.linspace(0, 0, N), scale=100), speed=opti.variable(np.linspace(100, 50, N)), gamma=opti.variable(np.linspace(0, 0, N)), track=opti.variable(np.linspace(0, 0, N)), ) else: raise ValueError("Bad value of `parameterization`!") if gravity: dyn.add_gravity_force() q = 0.5 * 1.225 * dyn.speed**2 if drag: dyn.add_force(Fx=-1 * (0.1)**2 * q, axes="wind") if sideforce: strouhal = 0.2 frequency = 5 # strouhal * dyn.speed / 0.1 dyn.add_force(Fy=q * 1 * (0.1)**2 * np.sin(2 * np.pi * frequency * time), axes="wind") dyn.constrain_derivatives( opti=opti, time=time, ) opti.subject_to([ dyn.x_e[0] == 0, dyn.y_e[0] == 0, dyn.z_e[0] == 0, dyn.u_e[0] == 100, dyn.v_e[0] == 0, dyn.w_e[0] == -100, ]) sol = opti.solve(verbose=verbose) dyn.substitute_solution(sol) if plot: import matplotlib.pyplot as plt import aerosandbox.tools.pretty_plots as p fig, ax = plt.subplots() p.plot_color_by_value(dyn.x_e, dyn.altitude, c=dyn.speed, colorbar=True) p.equal() p.show_plot("Trajectory", "$x_e$", "$z_e$") return dyn
opti = asb.Opti() # v_a = opti.variable(init_guess=15) # v_t = opti.variable(init_guess=15) # u_a = opti.variable(init_guess=5) Psi = opti.variable(init_guess=pi/2) # h_ati = opti.variable(init_guess=0.01) # f = opti.variable(init_guess=300) # F = opti.variable(init_guess=1) # gamma = opti.variable(init_guess=1) ### Define velocity triangle components U_a = airspeed #+ u_a # Axial velocity w/o induced eff. assuming u_a = 0 U_t = omega * radial_loc # Tangential velocity w/o induced eff. U = (U_a ** 2 + U_t ** 2) ** 0.5 # Velocity magnitude W_a = 0.5 * U_a + 0.5 * U * np.sin(Psi) # Axial velocity w/ induced eff. W_t = 0.5 * U_t + 0.5 * U * np.cos(Psi) # Tangential velocity w/ induced eff. v_a = W_a - U_a # Axial induced velocity v_t = U_t - W_t # Tangential induced velocity W = (W_a ** 2 + W_t ** 2) ** 0.5 Re = air_density * W * chord_local / mu Ma = W / speed_of_sound v = (v_a ** 2 + v_t ** 2) ** 0.5 loc_wake_adv_ratio = (radial_loc / tip_radius) * (W_a / W_t) f = (n_blades / 2) * (1 - radial_loc / tip_radius) * 1 / loc_wake_adv_ratio F = 2 / pi * np.arccos(np.exp(-f)) ## Compute local blade quantities phi_rad = np.arctan2(W_a, W_t) #local flow angle phi_deg = phi_rad * 180 / pi
def f(x): return 10 + x**2 + 10 * np.sin(x)
def rotation_matrix_3D(angle: Union[float, _onp.ndarray], axis: Union[_onp.ndarray, List, str], as_array: bool = True, axis_already_normalized: bool = False): """ Yields the rotation matrix that corresponds to a rotation by a specified amount about a given axis. An implementation of https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle Args: angle: The angle to rotate by. [radians] Direction of rotation corresponds to the right-hand rule. Can be vectorized. axis: The axis to rotate about. [ndarray] Can be vectorized; be sure axis[0] yields all the x-components, etc. as_array: boolean, returns a 3x3 array-like if True, and a list-of-lists otherwise. If you are intending to use this function vectorized, it is recommended you flag this False. (Or test before proceeding.) axis_already_normalized: boolean, skips axis normalization for speed if you flag this true. Returns: The rotation matrix, with type according to the parameter `as_array`. """ s = sin(angle) c = cos(angle) if isinstance(axis, str): if axis.lower() == "x": rot = [[1, 0, 0], [0, c, -s], [0, s, c]] elif axis.lower() == "y": rot = [[c, 0, s], [0, 1, 0], [-s, 0, c]] elif axis.lower() == "z": rot = [[c, -s, 0], [s, c, 0], [0, 0, 1]] else: raise ValueError( "If `axis` is a string, it must be `x`, `y`, or `z`.") else: ux = axis[0] uy = axis[1] uz = axis[2] if not axis_already_normalized: norm = (ux**2 + uy**2 + uz**2)**0.5 ux = ux / norm uy = uy / norm uz = uz / norm rot = [[ c + ux**2 * (1 - c), ux * uy * (1 - c) - uz * s, ux * uz * (1 - c) + uy * s ], [ uy * ux * (1 - c) + uz * s, c + uy**2 * (1 - c), uy * uz * (1 - c) - ux * s ], [ uz * ux * (1 - c) - uy * s, uz * uy * (1 - c) + ux * s, c + uz**2 * (1 - c) ]] if as_array: return array(rot) else: return rot
def firefly_CLA_and_CDA_fuse_hybrid( # TODO remove fuse_fineness_ratio, fuse_boattail_angle, fuse_TE_diameter, fuse_length, fuse_diameter, alpha, V, mach, rho, mu, ): """ Estimated equiv. lift area and equiv. drag area of the Firefly fuselage, component buildup. :param fuse_fineness_ratio: Fineness ratio of the fuselage nose (length / diameter). 0.5 is hemispherical. :param fuse_boattail_angle: Boattail half-angle [deg] :param fuse_TE_diameter: Diameter of the fuselage's base at the "trailing edge" [m] :param fuse_length: Length of the fuselage [m] :param fuse_diameter: Diameter of the fuselage [m] :param alpha: Angle of attack [deg] :param V: Airspeed [m/s] :param mach: Mach number [unitless] :param rho: Air density [kg/m^3] :param mu: Dynamic viscosity of air [Pa*s] :return: A tuple of (CLA, CDA) [m^2] """ alpha_rad = alpha * np.pi / 180 sin_alpha = np.sin(alpha_rad) cos_alpha = np.cos(alpha_rad) """ Lift of a truncated fuselage, following slender body theory. """ separation_location = 0.3 # 0 for separation at trailing edge, 1 for separation at start of boat-tail. Calibrate this. diameter_at_separation = ( 1 - separation_location ) * fuse_TE_diameter + separation_location * fuse_diameter fuse_TE_area = np.pi / 4 * diameter_at_separation**2 CLA_slender_body = 2 * fuse_TE_area * alpha_rad # Derived from FVA 6.6.5, Eq. 6.75 """ Crossflow force, following the method of Jorgensen, 1977: "Prediction of Static Aero. Chars. for Slender..." """ V_crossflow = V * sin_alpha Re_crossflow = rho * np.abs(V_crossflow) * fuse_diameter / mu mach_crossflow = mach * np.abs(sin_alpha) eta = 1 # TODO make this a function of overall fineness ratio Cdn = 0.2 # Taken from suggestion for supercritical cylinders in Hoerner's Fluid Dynamic Drag, pg. 3-11 S_planform = fuse_diameter * fuse_length CNA = eta * Cdn * S_planform * sin_alpha * np.abs(sin_alpha) CLA_crossflow = CNA * cos_alpha CDA_crossflow = CNA * sin_alpha CLA = CLA_slender_body + CLA_crossflow r""" Zero-lift_force drag_force Model derived from high-fidelity data at: C:\Projects\GitHub\firefly_aerodynamics\Design_Opt\studies\Circular Firefly Fuse CFD\Zero-Lift Drag """ c = np.array([ 112.57153128951402720758778741583, -7.1720570832587240417410612280946, -0.01765596807595304351679033061373, 0.0026135564778264172743071913629365, 550.8775012129947299399645999074, 3.3166868391027000129156476759817, 11774.081980549422951298765838146, 3073258.204571904614567756652832, 0.0299, ]) # coefficients fuse_Re = rho * np.abs(V) * fuse_length / mu CDA_zero_lift = ( (c[0] * np.exp(c[1] * fuse_fineness_ratio) + c[2] * fuse_boattail_angle + c[3] * fuse_boattail_angle**2 + c[4] * fuse_TE_diameter**2 + c[5]) / c[6] * (fuse_Re / c[7])**(-1 / 7) * (fuse_length * fuse_diameter) / c[8]) r""" Assumes a ring-shaped wake projection into the Trefftz plane with a sinusoidal circulation distribution. This results in a uniform grad(phi) within the ring in the Trefftz plane. Derivation at C:\Projects\GitHub\firefly_aerodynamics\Gists and Ideas\Ring Wing Potential Flow """ fuse_oswalds_efficiency = 0.5 CDiA_slender_body = CLA**2 / ( diameter_at_separation**2 * np.pi * fuse_oswalds_efficiency) / 2 # or equivalently, use the next line # CDiA_slender_body = fuse_TE_area * alpha_rad ** 2 / 2 CDA = CDA_crossflow + CDiA_slender_body + CDA_zero_lift return CLA, CDA