Example #1
0
    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
Example #2
0
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)
Example #3
0
 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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
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
Example #9
0
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
Example #10
0
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
Example #11
0
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
Example #12
0
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
Example #13
0
    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
Example #14
0
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
Example #15
0
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
Example #16
0
<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])
Example #17
0
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
Example #18
0
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