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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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()
Exemple #5
0
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())
Exemple #7
0
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,
        }
Exemple #10
0
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
Exemple #11
0
            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()
Exemple #12
0
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)
Exemple #15
0
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))))
Exemple #16
0
    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)
Exemple #18
0
def f(x):
    return np.sin(x) + 2 * x + 3
Exemple #19
0
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
Exemple #22
0
 def f(x):
     return 10 + x**2 + 10 * np.sin(x)
Exemple #23
0
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
Exemple #24
0
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