def calculate_derivatives(self, state, environment, controls, eps=1e-3): """ Calculate dimensional derivatives of the forces at the vicinity of the state. The output consists in 2 dictionaries, one for force one for moment key: type of variables derivatives are taken for val : 3x3 np array with X,Y,Z and L,M,N as columns, and the variable we differentiate against in lines (u,v,w ; phi,theta,psi ; p,q,r ; x,y,z) """ names = { 'velocity': ['u', 'v', 'w'], 'omega': ['p', 'q', 'r'], 'acceleration': ['w_dot'] } Fnames = ['X', 'Y', 'Z'] Mnames = ['L', 'M', 'N'] # F, M = self.calculate_forces_and_moments(state, environment, controls) # Rotation for stability derivatives in stability axis V = np.sqrt(state.velocity.u**2 + state.velocity.v**2 + state.velocity.w**2) alpha = np.arctan2(state.velocity.w, state.velocity.u) beta = np.arcsin(state.velocity.v / V) derivatives = {} for keyword in names.keys(): for i in range(len(names[keyword])): eps_v0 = np.zeros(3) # plus perturb eps_v0[i] = eps / 2 eps_vec = wind2body(eps_v0, alpha, beta) state.perturbate(eps_vec, keyword) forces_p, moments_p = self.calculate_forces_and_moments( state, environment, controls) forces_p = body2wind(forces_p, alpha, beta) moments_p = body2wind(moments_p, alpha, beta) state.cancel_perturbation() # minus perturb eps_v0[i] = -eps / 2 eps_vec = wind2body(eps_v0, alpha, beta) state.perturbate(eps_vec, keyword) forces_m, moments_m = self.calculate_forces_and_moments( state, environment, controls) forces_m = body2wind(forces_m, alpha, beta) moments_m = body2wind(moments_m, alpha, beta) state.cancel_perturbation() k = names[keyword][i] for j in range(3): # print(Fnames[j] + k, forces[j]) derivatives[Fnames[j] + k] = (forces_p[j] - forces_m[j]) / eps derivatives[Mnames[j] + k] = (moments_p[j] - moments_m[j]) / eps return derivatives
def test_wind2body(): # Test with an increment of the angle of attack vector_wind = np.array([1, 1, 1]) alpha, beta = np.deg2rad(45), 0 vector_body = wind2body(vector_wind, alpha, beta) vector_body_expected = np.array([0, 1, 2 * 0.70710678118654757]) assert_array_almost_equal(vector_body, vector_body_expected) # Test with an increment of the sideslip angle vector_wind = np.array([1, 1, 1]) alpha, beta = 0, np.deg2rad(45) vector_body = wind2body(vector_wind, alpha, beta) vector_body_expected = np.array([0, 2 * 0.70710678118654757, 1]) assert_array_almost_equal(vector_body, vector_body_expected)
def _calculate_forces_and_moments(self, state, conditions, controls): TAS = conditions.TAS alpha = conditions.alpha beta = conditions.beta Ft = self._calculate_thrust_forces_moments(TAS, conditions, controls) L, D, Y, l, m, n = self._calculate_aero_forces_moments( conditions, state, controls) Fa_wind = np.array([-D, Y, -L]) Fa_body = wind2body(Fa_wind, alpha, beta) Fa = Fa_body total_forces = Ft + Fa total_moments = np.array([l, m, n]) return total_forces, total_moments
def calculate_forces_and_moments(self): Ft = self._calculate_thrust_forces_moments() L, D, Y, l, m, n = self._calculate_aero_forces_moments() Fg = self.gravity_force Fa_wind = np.array([-D, Y, -L]) Fa_body = wind2body(Fa_wind, self.alpha, self.beta) Fa = Fa_body self.total_forces = Ft + Fg + Fa self.total_moments = np.array([l, m, n]) return self.total_forces, self.total_moments
def calculate_forces_and_moments(self, state, environment, controls): # Update controls and aerodynamics super().calculate_forces_and_moments(state, environment, controls) Ft = self._calculate_thrust_forces_moments(environment) L, D, Y, l, m, n = self._calculate_aero_forces_moments(state) Fg = environment.gravity_vector * self.mass Fa_wind = np.array([-D, Y, -L]) Fa_body = wind2body(Fa_wind, self.alpha, self.beta) Fa = Fa_body self.total_forces = Ft + Fg + Fa self.total_moments = np.array([l, m, n]) return self.total_forces, self.total_moments
def func(trimmed_params, h, TAS, gamma, turn_rate, aircraft, dynamic_eqs): """Function to optimize """ alpha = trimmed_params[0] beta = trimmed_params[1] delta_e = trimmed_params[2] delta_ail = trimmed_params[3] delta_r = trimmed_params[4] delta_t = trimmed_params[5] if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, TAS, gamma) theta = rate_of_climb_cons(gamma, alpha, beta, phi) # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = -turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) ang_vel = np.array([p, q, r]) lin_vel = wind2body((TAS, 0, 0), alpha, beta) # FIXME: This implied some changes in the aircraft model. # psi angle does not influence the attitude of the aircraft for gravity # force projection. So it is set to 0. attitude = np.array([theta, phi, 0]) _, _, rho, _ = atm(h) forces, moments = aircraft.get_forces_and_moments(TAS, rho, alpha, beta, delta_e, 0, delta_ail, delta_r, delta_t, attitude) mass, inertia = aircraft.mass_and_inertial_data() vel = np.concatenate((lin_vel[:], ang_vel[:])) output = dynamic_eqs(0, vel, mass, inertia, forces, moments) return output
def func(trimmed_params, h, TAS, gamma, turn_rate, aircraft, dynamic_eqs): """Function to optimize """ alpha = trimmed_params[0] beta = trimmed_params[1] delta_e = trimmed_params[2] delta_ail = trimmed_params[3] delta_r = trimmed_params[4] delta_t = trimmed_params[5] if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, TAS, gamma) theta = rate_of_climb_cons(gamma, alpha, beta, phi) # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = - turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) ang_vel = np.array([p, q, r]) lin_vel = wind2body((TAS, 0, 0), alpha, beta) # FIXME: This implied some changes in the aircraft model. # psi angle does not influence the attitude of the aircraft for gravity # force projection. So it is set to 0. attitude = np.array([theta, phi, 0]) _, _, rho, _ = atm(h) forces, moments = aircraft.get_forces_and_moments(TAS, rho, alpha, beta, delta_e, 0, delta_ail, delta_r, delta_t, attitude) mass, inertia = aircraft.mass_and_inertial_data() vel = np.concatenate((lin_vel[:], ang_vel[:])) output = dynamic_eqs(0, vel, mass, inertia, forces, moments) return output
def trimming_cost_func(trimmed_params, system, ac, env, controls2trim, gamma, turn_rate): """Function to optimize """ alpha = trimmed_params[0] beta = trimmed_params[1] new_controls = {} for ii, control in enumerate(controls2trim): new_controls[control] = trimmed_params[ii + 2] # Choose coordinated turn constrain equation: if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, ac.TAS, gamma) system.euler_angles[2] = phi # Rate of climb constrain theta = rate_of_climb_cons(gamma, alpha, beta, phi) system.euler_angles[1] = theta # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = -turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) system.vel_ang = np.array([p, q, r]) system.vel_body = wind2body((ac.TAS, 0, 0), alpha=alpha, beta=beta) env.update(system) ac.update(new_controls, system, env) forces, moments = ac.calculate_forces_and_moments() vel = np.concatenate((system.vel_body[:], system.vel_ang[:])) output = system.lamceq(0, vel, ac.mass(), ac.inertia(ac.cog(use_subcomponents=False)), forces, moments) return output
def trimming_cost_func(trimmed_params, system, aircraft, environment, controls2trim, gamma, turn_rate): """Function to optimize """ alpha = trimmed_params[0] beta = trimmed_params[1] new_controls = {} for ii, control in enumerate(controls2trim): new_controls[control] = trimmed_params[ii + 2] # Choose coordinated turn constrain equation: if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, aircraft.TAS, gamma) # Rate of climb constrain theta = rate_of_climb_cons(gamma, alpha, beta, phi) # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = - turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) u, v, w = wind2body((aircraft.TAS, 0, 0), alpha=alpha, beta=beta) psi = system.full_state.attitude.psi system.full_state.attitude.update([theta, phi, psi]) attitude = system.full_state.attitude system.full_state.velocity.update([u, v, w], attitude) system.full_state.angular_vel.update([p, q, r], attitude) system.full_state.acceleration.update([0, 0, 0], attitude) system.full_state.angular_accel.update([0, 0, 0], attitude) rv = system.steady_state_trim_fun(system.full_state, environment, aircraft, new_controls) return rv
def trimming_cost_func(trimmed_params, system, ac, env, controls2trim, gamma, turn_rate): """Function to optimize """ alpha = trimmed_params[0] beta = trimmed_params[1] new_controls = {} for ii, control in enumerate(controls2trim): new_controls[control] = trimmed_params[ii + 2] # Choose coordinated turn constrain equation: if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, ac.TAS, gamma) system.euler_angles[2] = phi # Rate of climb constrain theta = rate_of_climb_cons(gamma, alpha, beta, phi) system.euler_angles[1] = theta # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = - turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) system.vel_ang = np.array([p, q, r]) system.vel_body = wind2body((ac.TAS, 0, 0), alpha=alpha, beta=beta) env.update(system) ac.update(new_controls, system, env) forces, moments = ac.calculate_forces_and_moments() vel = np.concatenate((system.vel_body[:], system.vel_ang[:])) output = system.lamceq(0, vel, ac.mass, ac.inertia, forces, moments) return output
def calculate_forces_and_moments(self, beta_effect=True): """Compute the forces and moments of the global aircraft collecting all the subcomponents Parameters ---------- beta_effect : bool If True, the force and moment coeffs, CY, Cl and Cn will be multiplied by the sideslip angle. False otherwise. In general this is True for Wing and False for Flap Returns ------- f : array_like Drag, lateral and Lift forces (N) m : array_like Roll, pitch and yaw moments (N * m) """ f, m = super().calculate_forces_and_moments() # Get the aircraft data aircraft = self.top_node() assert isinstance(aircraft, Aircraft) attack_angles = np.asarray([ 0.0, aircraft.alpha, # rad aircraft.beta ]) # rad attack_angles_dot = np.asarray([ 0.0, aircraft.alpha_dot, # rad/s aircraft.beta_dot ]) # rad/s p = aircraft.p # rad/s q = aircraft.q # rad/s r = aircraft.r # rad/s # FIXME: Vectorial velocities should be considered to can model other # aircraft types, like helicopters V = aircraft.TAS # m/s q_inf = aircraft.q_inf # Pa # Get the angle of attack (the angle along the span direction) alpha = np.rad2deg(np.dot(attack_angles, self.__dir[1])) # deg alphadot = np.dot(attack_angles_dot, self.__dir[1]) # rad/s # Get the sideslip angle (the angle along the "z" direction) beta = np.dot(attack_angles, self.__dir[2]) # rad # Compute the forces from the force coefficients for Cf in self.__Cf: Sw = self.Sw() if self.__get_coeff_type(Cf) == 'f': ff = q_inf * Sw * self.__solve_coeff(Cf, alpha, beta, V, p, q, r, alphadot, beta_effect) f += wind2body(ff, aircraft.alpha, aircraft.beta) else: # The moments should be multiplied by the chord or the span d = np.dot((self.span, self.chord, self.span), self.__get_coeff_vec(Cf)) m += d * q_inf * Sw * self.__solve_coeff( Cf, alpha, beta, V, p, q, r, alphadot, beta_effect) return f, m
def get_aerodynamic_forces(lin_vel, ang_vel, TAS, rho, alpha, beta, magnus_effect=True): """Given the velocity vectors vel and ang_vel (body axes) it provides the forces (aerodynamic drag and Magnus effect if it exists) (body axes). Data for a soccer ball (smooth sphere). Parameters ---------- lin_vel : float array [u, v, w] air linear velocity body-axes (m/s). ang_vel : float array [p, q, r] air angular velocity body-axes (m/s). TAS : float true air speed (m/s) rho : float air density (kg/m3). alpha : float angle of attack (rad). beta : float sideslip angle (rad). magnus_effect : boolean True if magnus effect is under consideration. Returns ------- Total_aerodynamic_forces_body : float array Drag (+ magnus effect if exists) Forces vector (1x3) (body axes) (N). See Also -------- [2] Notes ----- Smooth ball selected. see[1] Reynolds vs CD_full table: +------------+------------+ | Re |CD_full_list| +============+============+ | 3.8e4 | 0.49 | +------------+------------+ |1.e5 | 0.51 | +------------+------------+ |100000 | 0.50 | +------------+------------+ |160000 | 0.51 | +------------+------------+ |200000 | 0.51 | +------------+------------+ |250000 | 0.49 | +------------+------------+ |300000 | 0.46 | +------------+------------+ |330000 | 0.39 | +------------+------------+ |350000 | 0.20 | +------------+------------+ |375000 | 0.09 | +------------+------------+ |400000 | 0.07 | +------------+------------+ |500000 | 0.07 | +------------+------------+ |800000 | 0.10 | +------------+------------+ |2000000 | 0.15 | +------------+------------+ |4000000 | 0.18 | +------------+------------+ Re : float Re = rho * TAS * radius / mu # Reynolds number. values between 38e3 and 4e6. References ---------- .. [1] "Aerodynamics of Sports Balls" Bruce D. Jothmann, January 2007 .. [2] "Aerodynamics of Sports Balls" Annual Review of Fluid Mechanics, 1875.17:15 Mehta, R.D. """ u, v, w = lin_vel # components of the linear velocity p, q, r = ang_vel # components of the angular velocity radius, A_front, _, _ = geometric_data() Re = rho * TAS * radius / mu # Reynolds number check_reynolds_number(Re) # %Obtaining of Drag coefficient and Drag force CD_full = np.interp(Re, Re_list, CD_full_list) D = 0.5 * rho * TAS**2 * A_front * CD_full D_vector_body = wind2body(([-D, 0, 0]), alpha, beta) # %It adds or not the magnus effect, depending on the variable # % magnus_effect if magnus_effect: F_magnus_vector_body = get_magnus_effect_forces( lin_vel, ang_vel, TAS, rho, radius, A_front, alpha, beta) total_aerodynamic_forces_body = D_vector_body + F_magnus_vector_body return total_aerodynamic_forces_body else: total_aerodynamic_forces_body = D_vector_body return total_aerodynamic_forces_body
def steady_state_flight_trim(aircraft, h, TAS, gamma=0, turn_rate=0, dyn_eqs=None, verbose=0): """Finds a combination of values of the state and control variables that correspond to a steady-state flight condition. Steady-state aircraft flight can be defined as a condition in which all of the motion variables are constant or zero. That is, the linear and angular velocity components are constant (or zero), and all acceleration components are zero. Parameters ---------- aircraft : aircraft class Aircraft class with methods get_forces, get_moments h : float Geopotential altitude for ISA (m). TAS : float True Air Speed (m/s). gamma : float, optional Flight path angle (rad). turn_rate : float, optional Turn rate, d(psi)/dt (rad/s). Returns ------- lin_vel : float array [u, v, w] air linear velocity body-axes (m/s). ang_vel : float array [p, q, r] air angular velocity body-axes (m/s). theta : float Pitch angle (rad). phi : float Bank angle (rad). alpha : float Angle of attack (rad). beta : float Sideslip angle (rad). control_vector : array_like [delta_e, delta_ail, delta_r, delta_t]. Notes ----- See section 3.4 in [1] for the algorithm description. See section 2.5 in [1] for the definition of steady-state flight condition. References ---------- .. [1] Stevens, BL and Lewis, FL, "Aircraft Control and Simulation", Wiley-lnterscience. """ if dyn_eqs is None: from pyfme.models.euler_flat_earth import linear_and_angular_momentum_eqs dynamic_eqs = linear_and_angular_momentum_eqs # TODO: try to look for a good inizialization method alpha_0 = 0.05 * np.sign(gamma) betha_0 = 0.001 * np.sign(turn_rate) delta_e_0 = 0.05 delta_ail_0 = 0.01 * np.sign(turn_rate) delta_r_0 = 0.01 * np.sign(turn_rate) delta_t_0 = 0.5 initial_guess = (alpha_0, betha_0, delta_e_0, delta_ail_0, delta_r_0, delta_t_0) args = (h, TAS, gamma, turn_rate, aircraft, dynamic_eqs) # TODO: pass max deflection of the controls inside aircraft. lower_bounds = (-1, -0.5, -1, -1, -1, 0) upper_bounds = (+1, +0.5, +1, +1, +1, 1) results = least_squares(func, x0=initial_guess, args=args, verbose=verbose, bounds=(lower_bounds, upper_bounds)) trimmed_params = results['x'] fun = results['fun'] cost = results['cost'] if cost > 1e-7 or any(abs(fun) > 1e-3): warn("Trim process did not converge", RuntimeWarning) if trimmed_params[5] > 0.99: warn("Probably not enough power for demanded conditions") alpha = trimmed_params[0] beta = trimmed_params[1] delta_e = trimmed_params[2] delta_ail = trimmed_params[3] delta_r = trimmed_params[4] delta_t = trimmed_params[5] # What happens if we have two engines and all that kind of things...? control_vector = delta_e, delta_ail, delta_r, delta_t if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, TAS, gamma) theta = rate_of_climb_cons(gamma, alpha, beta, phi) # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = - turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) ang_vel = np.array([p, q, r]) lin_vel = wind2body((TAS, 0, 0), alpha, beta) return lin_vel, ang_vel, theta, phi, alpha, beta, control_vector
<Module name> <Brief description ...> """ from pyfme.aircrafts import cessna_310 from pyfme.utils.trimmer import steady_state_flight_trim from pyfme.utils.coordinates import wind2body TAS = 80 # m/s h = 1000 results = steady_state_flight_trim(cessna_310, h, TAS, gamma=0, turn_rate=0) # lin_vel, ang_vel, theta, phi, alpha, beta, control_vector alpha = results[3] beta = results[4] lin_vel = wind2body((TAS, 0, 0), alpha, beta) print( """ Results ------- Linear velocity: {lin_vel} (m/s) Angular velocity: {ang_vel} (rad/s) Theta, Phi: {angles} (rad) Alpha, Beta: {wind_angles} (rad) Control: {control} """.format(lin_vel=lin_vel, ang_vel=results[1], angles=results[2:4], wind_angles=results[4:6], control=results[6])
def steady_state_flight_trim(aircraft, h, TAS, gamma=0, turn_rate=0, dyn_eqs=None, verbose=0): """Finds a combination of values of the state and control variables that correspond to a steady-state flight condition. Steady-state aircraft flight can be defined as a condition in which all of the motion variables are constant or zero. That is, the linear and angular velocity components are constant (or zero), and all acceleration components are zero. Parameters ---------- aircraft : aircraft class Aircraft class with methods get_forces, get_moments h : float Geopotential altitude for ISA (m). TAS : float True Air Speed (m/s). gamma : float, optional Flight path angle (rad). turn_rate : float, optional Turn rate, d(psi)/dt (rad/s). Returns ------- lin_vel : float array [u, v, w] air linear velocity body-axes (m/s). ang_vel : float array [p, q, r] air angular velocity body-axes (m/s). theta : float Pitch angle (rad). phi : float Bank angle (rad). alpha : float Angle of attack (rad). beta : float Sideslip angle (rad). control_vector : array_like [delta_e, delta_ail, delta_r, delta_t]. Notes ----- See section 3.4 in [1] for the algorithm description. See section 2.5 in [1] for the definition of steady-state flight condition. References ---------- .. [1] Stevens, BL and Lewis, FL, "Aircraft Control and Simulation", Wiley-lnterscience. """ if dyn_eqs is None: from pyfme.models.euler_flat_earth import linear_and_angular_momentum_eqs dynamic_eqs = linear_and_angular_momentum_eqs # TODO: try to look for a good inizialization method alpha_0 = 0.05 * np.sign(gamma) betha_0 = 0.001 * np.sign(turn_rate) delta_e_0 = 0.05 delta_ail_0 = 0.01 * np.sign(turn_rate) delta_r_0 = 0.01 * np.sign(turn_rate) delta_t_0 = 0.5 initial_guess = (alpha_0, betha_0, delta_e_0, delta_ail_0, delta_r_0, delta_t_0) args = (h, TAS, gamma, turn_rate, aircraft, dynamic_eqs) # TODO: pass max deflection of the controls inside aircraft. lower_bounds = (-1, -0.5, -1, -1, -1, 0) upper_bounds = (+1, +0.5, +1, +1, +1, 1) results = least_squares(func, x0=initial_guess, args=args, verbose=verbose, bounds=(lower_bounds, upper_bounds)) trimmed_params = results['x'] fun = results['fun'] cost = results['cost'] if cost > 1e-7 or any(abs(fun) > 1e-3): warn("Trim process did not converge", RuntimeWarning) if trimmed_params[5] > 0.99: warn("Probably not enough power for demanded conditions") alpha = trimmed_params[0] beta = trimmed_params[1] delta_e = trimmed_params[2] delta_ail = trimmed_params[3] delta_r = trimmed_params[4] delta_t = trimmed_params[5] # What happens if we have two engines and all that kind of things...? control_vector = delta_e, delta_ail, delta_r, delta_t if abs(turn_rate) < 1e-8: phi = 0 else: phi = turn_coord_cons(turn_rate, alpha, beta, TAS, gamma) theta = rate_of_climb_cons(gamma, alpha, beta, phi) # w = turn_rate * k_h # k_h = sin(theta) i_b + sin(phi) * cos(theta) j_b + cos(theta) * sin(phi) # w = p * i_b + q * j_b + r * k_b p = -turn_rate * sin(theta) q = turn_rate * sin(phi) * cos(theta) r = turn_rate * cos(theta) * sin(phi) ang_vel = np.array([p, q, r]) lin_vel = wind2body((TAS, 0, 0), alpha, beta) return lin_vel, ang_vel, theta, phi, alpha, beta, control_vector
def get_aerodynamic_forces(lin_vel, ang_vel, TAS, rho, alpha, beta, magnus_effect=True): """Given the velocity vectors vel and ang_vel (body axes) it provides the forces (aerodynamic drag and Magnus effect if it exists) (body axes). Data for a soccer ball (smooth sphere). Parameters ---------- lin_vel : float array [u, v, w] air linear velocity body-axes (m/s). ang_vel : float array [p, q, r] air angular velocity body-axes (m/s). TAS : float true air speed (m/s) rho : float air density (kg/m3). alpha : float angle of attack (rad). beta : float sideslip angle (rad). magnus_effect : boolean True if magnus effect is under consideration. Returns ------- Total_aerodynamic_forces_body : float array Drag (+ magnus effect if exists) Forces vector (1x3) (body axes) (N). See Also -------- [2] Notes ----- Smooth ball selected. see[1] Reynolds vs CD_full table: +------------+------------+ | Re |CD_full_list| +============+============+ | 3.8e4 | 0.49 | +------------+------------+ |1.e5 | 0.51 | +------------+------------+ |100000 | 0.50 | +------------+------------+ |160000 | 0.51 | +------------+------------+ |200000 | 0.51 | +------------+------------+ |250000 | 0.49 | +------------+------------+ |300000 | 0.46 | +------------+------------+ |330000 | 0.39 | +------------+------------+ |350000 | 0.20 | +------------+------------+ |375000 | 0.09 | +------------+------------+ |400000 | 0.07 | +------------+------------+ |500000 | 0.07 | +------------+------------+ |800000 | 0.10 | +------------+------------+ |2000000 | 0.15 | +------------+------------+ |4000000 | 0.18 | +------------+------------+ Re : float Re = rho * TAS * radius / mu # Reynolds number. values between 38e3 and 4e6. References ---------- .. [1] "Aerodynamics of Sports Balls" Bruce D. Jothmann, January 2007 .. [2] "Aerodynamics of Sports Balls" Annual Review of Fluid Mechanics, 1875.17:15 Mehta, R.D. """ u, v, w = lin_vel # components of the linear velocity p, q, r = ang_vel # components of the angular velocity radius, A_front, _, _ = geometric_data() Re = rho * TAS * radius / mu # Reynolds number check_reynolds_number(Re) # %Obtaining of Drag coefficient and Drag force CD_full = np.interp(Re, Re_list, CD_full_list) D = 0.5 * rho * TAS ** 2 * A_front * CD_full D_vector_body = wind2body(([-D, 0, 0]), alpha, beta) # %It adds or not the magnus effect, depending on the variable # % magnus_effect if magnus_effect: F_magnus_vector_body = get_magnus_effect_forces(lin_vel, ang_vel, TAS, rho, radius, A_front, alpha, beta) total_aerodynamic_forces_body = D_vector_body + F_magnus_vector_body return total_aerodynamic_forces_body else: total_aerodynamic_forces_body = D_vector_body return total_aerodynamic_forces_body