Пример #1
0
def Cd_cylinder(Re_D: float, subcritical_only=False) -> float:
    """
    Returns the drag coefficient of a cylinder in crossflow as a function of its Reynolds number.
    :param Re_D: Reynolds number, referenced to diameter
    :param subcritical_only: Determines whether the model models purely subcritical (Re < 300k) cylinder flows. Useful, since
    this model is now convex and can be more well-behaved.
    :return: Drag coefficient

    # TODO rework this function to use tanh blending, which will mitigate overflows
    """
    csigc = 5.5766722118597247
    csigh = 23.7460859935990563
    csub0 = -0.6989492360435040
    csub1 = 1.0465189382830078
    csub2 = 0.7044228755898569
    csub3 = 0.0846501115443938
    csup0 = -0.0823564417206403
    csupc = 6.8020230357616764
    csuph = 9.9999999999999787
    csupscl = -0.4570690347113859

    x = np.log10(np.abs(Re_D) + 1e-16)

    if subcritical_only:
        Cd = 10**(csub0 * x + csub1) + csub2 + csub3 * x
    else:
        log10_Cd = (
            (np.log10(10**(csub0 * x + csub1) + csub2 + csub3 * x)) *
            (1 - 1 / (1 + np.exp(-csigh * (x - csigc)))) +
            (csup0 + csupscl / csuph * np.log(np.exp(csuph *
                                                     (csupc - x)) + 1)) *
            (1 / (1 + np.exp(-csigh * (x - csigc)))))
        Cd = 10**log10_Cd

    return Cd
Пример #2
0
def Cf_flat_plate(Re_L: float, method="hybrid-sharpe-convex") -> float:
    """
    Returns the mean skin friction coefficient over a flat plate.

    Don't forget to double it (two sides) if you want a drag coefficient.

    Args:

        Re_L: Reynolds number, normalized to the length of the flat plate.

        method: The method of computing the skin friction coefficient. One of:

            * "blasius": Uses the Blasius solution. Citing Cengel and Cimbala, "Fluid Mechanics: Fundamentals and
            Applications", Table 10-4.

                Valid approximately for Re_L <= 5e5.

            * "turbulent": Uses turbulent correlations for smooth plates. Citing Cengel and Cimbala,
            "Fluid Mechanics: Fundamentals and Applications", Table 10-4.

                Valid approximately for 5e5 <= Re_L <= 1e7.

            * "hybrid-cengel": Uses turbulent correlations for smooth plates, but accounts for a
            non-negligible laminar run at the beginning of the plate. Citing Cengel and Cimbala, "Fluid Mechanics:
            Fundamentals and Applications", Table 10-4. Returns: Mean skin friction coefficient over a flat plate.

                Valid approximately for 5e5 <= Re_L <= 1e7.

            * "hybrid-schlichting": Schlichting's model, that roughly accounts for a non-negligtible laminar run.
            Citing "Boundary Layer Theory" 7th Ed., pg. 644

            * "hybrid-sharpe-convex": A hybrid model that blends the Blasius and Schlichting models. Convex in
            log-log space; however, it may overlook some truly nonconvex behavior near transitional Reynolds numbers.

            * "hybrid-sharpe-nonconvex": A hybrid model that blends the Blasius and Cengel models. Nonconvex in
            log-log-space; however, it may capture some truly nonconvex behavior near transitional Reynolds numbers.

    You can view all of these functions graphically using
    `aerosandbox.library.aerodynamics.test_aerodynamics.test_Cf_flat_plate.py`

    """
    Re_L = np.abs(Re_L)

    if method == "blasius":
        return 1.328 / Re_L**0.5
    elif method == "turbulent":
        return 0.074 / Re_L**(1 / 5)
    elif method == "hybrid-cengel":
        return 0.074 / Re_L**(1 / 5) - 1742 / Re_L
    elif method == "hybrid-schlichting":
        return 0.02666 * Re_L**-0.139
    elif method == "hybrid-sharpe-convex":
        return np.softmax(Cf_flat_plate(Re_L, method="blasius"),
                          Cf_flat_plate(Re_L, method="hybrid-schlichting"),
                          hardness=1e3)
    elif method == "hybrid-sharpe-nonconvex":
        return np.softmax(Cf_flat_plate(Re_L, method="blasius"),
                          Cf_flat_plate(Re_L, method="hybrid-cengel"),
                          hardness=1e3)
Пример #3
0
def Cl_flat_plate(alpha, Re_c):
    """
    Returns the approximate lift coefficient of a flat plate, following thin airfoil theory.
    :param alpha: Angle of attack [deg]
    :param Re_c: Reynolds number, normalized to the length of the flat plate.
    :return: Approximate lift coefficient.
    """
    Re_c = np.abs(Re_c)
    alpha_rad = alpha * np.pi / 180
    return 2 * np.pi * alpha_rad
Пример #4
0
def test_block_move_minimum_time():
    opti = asb.Opti()

    n_timesteps = 300

    time = np.linspace(
        0,
        opti.variable(init_guess=1, lower_bound=0),
        n_timesteps,
    )

    dyn = asb.DynamicsPointMass1DHorizontal(
        mass_props=asb.MassProperties(mass=1),
        x_e=opti.variable(init_guess=np.linspace(0, 1, n_timesteps)),
        u_e=opti.variable(init_guess=1, n_vars=n_timesteps),
    )

    u = opti.variable(init_guess=np.linspace(1, -1, n_timesteps), lower_bound=-1, upper_bound=1)

    dyn.add_force(
        Fx=u
    )

    dyn.constrain_derivatives(
        opti=opti,
        time=time
    )

    opti.subject_to([
        dyn.x_e[0] == 0,
        dyn.x_e[-1] == 1,
        dyn.u_e[0] == 0,
        dyn.u_e[-1] == 0,
    ])

    opti.minimize(
        time[-1]
    )

    sol = opti.solve()

    dyn.substitute_solution(sol)

    assert dyn.x_e[0] == pytest.approx(0)
    assert dyn.x_e[-1] == pytest.approx(1)
    assert dyn.u_e[0] == pytest.approx(0)
    assert dyn.u_e[-1] == pytest.approx(0)
    assert np.max(dyn.u_e) == pytest.approx(1, abs=0.01)
    assert sol.value(u)[0] == pytest.approx(1, abs=0.05)
    assert sol.value(u)[-1] == pytest.approx(-1, abs=0.05)
    assert np.mean(np.abs(sol.value(u))) == pytest.approx(1, abs=0.01)
Пример #5
0
def spy(
    matrix,
    show=True,
):
    """
    Plots the sparsity pattern of a matrix.
    :param matrix: The matrix to plot the sparsity pattern of. [2D ndarray or CasADi array]
    :param show: Whether or not to show the sparsity plot. [boolean]
    :return: The figure to be plotted [go.Figure]
    """
    try:
        matrix = matrix.toarray()
    except:
        pass
    abs_m = np.abs(matrix)
    sparsity_pattern = abs_m >= 1e-16
    matrix[sparsity_pattern] = np.log10(abs_m[sparsity_pattern] + 1e-16)
    j_index_map, i_index_map = np.meshgrid(np.arange(matrix.shape[1]),
                                           np.arange(matrix.shape[0]))

    i_index = i_index_map[sparsity_pattern]
    j_index = j_index_map[sparsity_pattern]
    val = matrix[sparsity_pattern]
    val = np.ones_like(i_index)
    fig = go.Figure(
        data=go.Heatmap(
            y=i_index,
            x=j_index,
            z=val,
            # type='heatmap',
            colorscale='RdBu',
            showscale=False,
        ), )
    fig.update_layout(
        plot_bgcolor="black",
        xaxis=dict(showgrid=False, zeroline=False),
        yaxis=dict(showgrid=False,
                   zeroline=False,
                   autorange="reversed",
                   scaleanchor="x",
                   scaleratio=1),
        width=800,
        height=800 * (matrix.shape[0] / matrix.shape[1]),
    )
    if show:
        fig.show()
    return fig
Пример #6
0
    def steady_state_performance(self, speed, grade=0, headwind=0):
        throttle = optimize.minimize(
            fun=lambda throttle:
            (self.performance(speed=speed,
                              throttle_state=throttle,
                              grade=grade,
                              headwind=headwind)['net acceleration'])**2,
            x0=np.array([0.5]),
            bounds=[(0, 1)]).x

        perf = self.performance(
            speed=speed,
            throttle_state=throttle,
            grade=grade,
            headwind=headwind,
        )

        if throttle > 1 or throttle < 0 or np.abs(
                perf['net acceleration']) >= 1e-6:
            raise ValueError(
                "Could not satisfy zero-net-acceleration condition!")

        return perf
Пример #7
0
    def goodness_of_fit(self, type="R^2"):
        """
        Returns a metric of the goodness of the fit.

        Args:

            type: Type of metric to use for goodness of fit. One of:

                * "R^2": The coefficient of determination. Strictly speaking only mathematically rigorous to use this
                for linear fits.

                    https://en.wikipedia.org/wiki/Coefficient_of_determination

                * "deviation" or "Linf": The maximum deviation of the fit from any of the data points.

        Returns: The metric of the goodness of the fit.

        """
        if type == "R^2":

            y_mean = np.mean(self.y_data)

            SS_tot = np.sum((self.y_data - y_mean)**2)

            y_model = self(self.x_data)

            SS_res = np.sum((self.y_data - y_model)**2)

            R_squared = 1 - SS_res / SS_tot

            return R_squared

        elif type == "deviation" or type == "Linf":
            return np.max(np.abs(self.y_data - self(self.x_data)))

        else:
            raise ValueError("Bad value of `type`!")
Пример #8
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.sind(alpha)
    cos_alpha = np.cosd(alpha)
    """
    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
Пример #9
0
def test_rocket():
    ### Environment
    opti = asb.Opti()

    ### Time discretization
    N = 500  # Number of discretization points
    time_final = 100  # seconds
    time = np.linspace(0, time_final, N)

    ### Constants
    mass_initial = 500e3  # Initial mass, 500 metric tons
    z_e_final = -100e3  # Final altitude, 100 km
    g = 9.81  # Gravity, m/s^2
    alpha = 1 / (
        300 * g
    )  # kg/(N*s), Inverse of specific impulse, basically - don't worry about this

    dyn = asb.DynamicsPointMass1DVertical(
        mass_props=asb.MassProperties(
            mass=opti.variable(init_guess=mass_initial, n_vars=N)),
        z_e=opti.variable(
            init_guess=np.linspace(0, z_e_final, N)
        ),  # Altitude (negative due to Earth-axes convention)
        w_e=opti.variable(init_guess=-z_e_final / time_final,
                          n_vars=N),  # Velocity
    )

    dyn.add_gravity_force(g=g)
    thrust = opti.variable(init_guess=g * mass_initial, n_vars=N)
    dyn.add_force(Fz=-thrust)

    dyn.constrain_derivatives(
        opti=opti,
        time=time,
    )

    ### Fuel burn
    opti.constrain_derivative(
        derivative=-alpha * thrust,
        variable=dyn.mass_props.mass,
        with_respect_to=time,
        method="midpoint",
    )

    ### Boundary conditions
    opti.subject_to([
        dyn.z_e[0] == 0,
        dyn.w_e[0] == 0,
        dyn.mass_props.mass[0] == mass_initial,
        dyn.z_e[-1] == z_e_final,
    ])

    ### Path constraints
    opti.subject_to([dyn.mass_props.mass >= 0, thrust >= 0])

    ### Objective
    opti.minimize(-dyn.mass_props.mass[-1]
                  )  # Maximize the final mass == minimize fuel expenditure

    ### Solve
    sol = opti.solve(verbose=False)
    print(f"Solved in {sol.stats()['iter_count']} iterations.")
    dyn.substitute_solution(sol)

    assert dyn.mass_props.mass[-1] == pytest.approx(290049.81034472014,
                                                    rel=0.05)
    assert np.abs(dyn.w_e).max() == pytest.approx(1448, rel=0.05)
Пример #10
0
def ln(x):
    return np.log(np.abs(x))
Пример #11
0
    def fuselage_aerodynamics(
        self,
        fuselage: Fuselage,
    ):
        """
        Estimates the aerodynamic forces, moments, and derivatives on a fuselage in isolation.

        Assumes:
            * The fuselage is a body of revolution aligned with the x_b axis.
            * The angle between the nose and the freestream is less than 90 degrees.

        Moments are given with the reference at Fuselage [0, 0, 0].

        Uses methods from Jorgensen, Leland Howard. "Prediction of Static Aerodynamic Characteristics for Slender Bodies
        Alone and with Lifting Surfaces to Very High Angles of Attack". NASA TR R-474. 1977.

        Args:

            fuselage: A Fuselage object that you wish to analyze.

        Returns:

        """
        ##### Alias a few things for convenience
        op_point = self.op_point
        Re = op_point.reynolds(reference_length=fuselage.length())
        fuse_options = self.get_options(fuselage)

        ####### Reference quantities (Set these 1 here, just so we can follow Jorgensen syntax.)
        # Outputs of this function should be invariant of these quantities, if normalization has been done correctly.
        S_ref = 1  # m^2
        c_ref = 1  # m

        ####### Fuselage zero-lift drag estimation

        ### Forebody drag
        C_f_forebody = aerolib.Cf_flat_plate(Re_L=Re)

        ### Base Drag
        C_D_base = 0.029 / np.sqrt(C_f_forebody) * fuselage.area_base() / S_ref

        ### Skin friction drag
        C_D_skin = C_f_forebody * fuselage.area_wetted() / S_ref

        ### Wave drag
        if self.include_wave_drag:
            sears_haack_drag = transonic.sears_haack_drag_from_volume(
                volume=fuselage.volume(), length=fuselage.length())
            C_D_wave = transonic.approximate_CD_wave(
                mach=op_point.mach(),
                mach_crit=critical_mach(
                    fineness_ratio_nose=fuse_options["nose_fineness_ratio"]),
                CD_wave_at_fully_supersonic=fuse_options["E_wave_drag"] *
                sears_haack_drag,
            )
        else:
            C_D_wave = 0

        ### Total zero-lift drag
        C_D_zero_lift = C_D_skin + C_D_base + C_D_wave

        ####### Jorgensen model

        ### First, merge the alpha and beta into a single "generalized alpha", which represents the degrees between the fuselage axis and the freestream.
        x_w, y_w, z_w = op_point.convert_axes(1,
                                              0,
                                              0,
                                              from_axes="body",
                                              to_axes="wind")
        generalized_alpha = np.arccosd(x_w / (1 + 1e-14))
        sin_generalized_alpha = np.sind(generalized_alpha)
        cos_generalized_alpha = x_w

        # ### Limit generalized alpha to -90 < alpha < 90, for now.
        # generalized_alpha = np.clip(generalized_alpha, -90, 90)
        # # TODO make the drag/moment functions not give negative results for alpha > 90.

        alpha_fractional_component = -z_w / np.sqrt(
            y_w**2 + z_w**2 + 1e-16
        )  # The fraction of any "generalized lift" to be in the direction of alpha
        beta_fractional_component = y_w / np.sqrt(
            y_w**2 + z_w**2 + 1e-16
        )  # The fraction of any "generalized lift" to be in the direction of beta

        ### Compute normal quantities
        ### Note the (N)ormal, (A)ligned coordinate system. (See Jorgensen for definitions.)
        # M_n = sin_generalized_alpha * op_point.mach()
        Re_n = sin_generalized_alpha * Re
        # V_n = sin_generalized_alpha * op_point.velocity
        q = op_point.dynamic_pressure()
        x_nose = fuselage.xsecs[0].xyz_c[0]
        x_m = 0 - x_nose
        x_c = fuselage.x_centroid_projected() - x_nose

        ##### Potential flow crossflow model
        C_N_p = (  # Normal force coefficient due to potential flow. (Jorgensen Eq. 2.12, part 1)
            fuselage.area_base() / S_ref * np.sind(2 * generalized_alpha) *
            np.cosd(generalized_alpha / 2))
        C_m_p = ((fuselage.volume() - fuselage.area_base() *
                  (fuselage.length() - x_m)) / (S_ref * c_ref) *
                 np.sind(2 * generalized_alpha) *
                 np.cosd(generalized_alpha / 2))

        ##### Viscous crossflow model
        C_d_n = np.where(
            Re_n != 0,
            aerolib.Cd_cylinder(
                Re_D=Re_n
            ),  # Replace with 1.20 from Jorgensen Table 1 if not working well
            0)
        eta = jorgensen_eta(fuselage.fineness_ratio())

        C_N_v = (  # Normal force coefficient due to viscous crossflow. (Jorgensen Eq. 2.12, part 2)
            eta * C_d_n * fuselage.area_projected() / S_ref *
            sin_generalized_alpha**2)
        C_m_v = (eta * C_d_n * fuselage.area_projected() / S_ref *
                 (x_m - x_c) / c_ref * sin_generalized_alpha**2)

        ##### Total C_N model
        C_N = C_N_p + C_N_v
        C_m_generalized = C_m_p + C_m_v

        ##### Total C_A model
        C_A = C_D_zero_lift * cos_generalized_alpha * np.abs(
            cos_generalized_alpha)

        ##### Convert to lift, drag
        C_L_generalized = C_N * cos_generalized_alpha - C_A * sin_generalized_alpha
        C_D = C_N * sin_generalized_alpha + C_A * cos_generalized_alpha

        ### Set proper directions

        C_L = C_L_generalized * alpha_fractional_component
        C_Y = -C_L_generalized * beta_fractional_component
        C_l = 0
        C_m = C_m_generalized * alpha_fractional_component
        C_n = -C_m_generalized * beta_fractional_component

        ### Un-normalize
        L = C_L * q * S_ref
        Y = C_Y * q * S_ref
        D = C_D * q * S_ref
        l_w = C_l * q * S_ref * c_ref
        m_w = C_m * q * S_ref * c_ref
        n_w = C_n * q * S_ref * c_ref

        ### Convert to axes coordinates for reporting
        F_w = (-D, Y, -L)
        F_b = op_point.convert_axes(*F_w, from_axes="wind", to_axes="body")
        F_g = op_point.convert_axes(*F_b, from_axes="body", to_axes="geometry")
        M_w = (
            l_w,
            m_w,
            n_w,
        )
        M_b = op_point.convert_axes(*M_w, from_axes="wind", to_axes="body")
        M_g = op_point.convert_axes(*M_b, from_axes="body", to_axes="geometry")

        return {
            "F_g": F_g,
            "F_b": F_b,
            "F_w": F_w,
            "M_g": M_g,
            "M_b": M_b,
            "M_w": M_w,
            "L": -F_w[2],
            "Y": F_w[1],
            "D": -F_w[0],
            "l_b": M_b[0],
            "m_b": M_b[1],
            "n_b": M_b[2]
        }
Пример #12
0
)
H = opti.variable(
    H_0,
    n_vars=N,
)

Re_theta = ue * theta / nu

H_star = np.where(
    H < 4,
    1.515 + 0.076 * (H - 4) ** 2 / H,
    1.515 + 0.040 * (H - 4) ** 2 / H
)  # From AVF Eq. 4.53
c_f = 2 / Re_theta * np.where(
    H < 6.2,
    -0.066 + 0.066 * np.abs(6.2 - H) ** 1.5 / (H - 1),
    -0.066 + 0.066 * (H - 6.2) ** 2 / (H - 4) ** 2
)  # From AVF Eq. 4.54
c_D = H_star / 2 / Re_theta * np.where(
    H < 4,
    0.207 + 0.00205 * np.abs(4 - H) ** 5.5,
    0.207 - 0.100 * (H - 4) ** 2 / H ** 2
)  # From AVF Eq. 4.55
Re_theta_o = 10 ** (
        2.492 / (H - 1) ** 0.43 +
        0.7 * (
                np.tanh(
                    14 / (H - 1) - 9.24
                ) + 1
        )
)  # From AVF Eq. 6.38
Пример #13
0
def Cd_cylinder(Re_D: float,
                mach: float = 0.,
                include_mach_effects=True,
                subcritical_only=False) -> float:
    """
    Returns the drag coefficient of a cylinder in crossflow as a function of its Reynolds number and Mach.

    Args:
        Re_D: Reynolds number, referenced to diameter
        mach: Mach number
        include_mach_effects: If this is set False, it assumes Mach = 0, which simplifies the computation.
        subcritical_only: Determines whether the model models purely subcritical (Re < 300k) cylinder flows. Useful, since
    this model is now convex and can be more well-behaved.

    Returns:

    # TODO rework this function to use tanh blending, which will mitigate overflows

    """

    ##### Do the viscous part of the computation
    csigc = 5.5766722118597247
    csigh = 23.7460859935990563
    csub0 = -0.6989492360435040
    csub1 = 1.0465189382830078
    csub2 = 0.7044228755898569
    csub3 = 0.0846501115443938
    csup0 = -0.0823564417206403
    csupc = 6.8020230357616764
    csuph = 9.9999999999999787
    csupscl = -0.4570690347113859

    x = np.log10(np.abs(Re_D) + 1e-16)

    if subcritical_only:
        Cd_mach_0 = 10**(csub0 * x + csub1) + csub2 + csub3 * x
    else:
        log10_Cd = (
            (np.log10(10**(csub0 * x + csub1) + csub2 + csub3 * x)) *
            (1 - 1 / (1 + np.exp(-csigh * (x - csigc)))) +
            (csup0 + csupscl / csuph * np.log(np.exp(csuph *
                                                     (csupc - x)) + 1)) *
            (1 / (1 + np.exp(-csigh * (x - csigc)))))
        Cd_mach_0 = 10**log10_Cd

    ##### Do the compressible part of the computation
    if include_mach_effects:
        m = mach
        p = {
            'a_sub': 0.03458900259594298,
            'a_sup': -0.7129528087049688,
            'cd_sub': 1.163206940186374,
            'cd_sup': 1.2899213533122527,
            's_sub': 3.436601777569716,
            's_sup': -1.37123096976983,
            'trans': 1.022819211244295,
            'trans_str': 19.017600596069848
        }

        Cd_over_Cd_mach_0 = np.blend(
            p["trans_str"] *
            (m - p["trans"]), p["cd_sup"] + np.exp(p["a_sup"] + p["s_sup"] *
                                                   (m - p["trans"])),
            p["cd_sub"] + np.exp(p["a_sub"] + p["s_sub"] *
                                 (m - p["trans"]))) / 1.1940010047391572

        Cd = Cd_mach_0 * Cd_over_Cd_mach_0

    else:
        Cd = Cd_mach_0

    return Cd
Пример #14
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,
        }
Пример #15
0
opti = asb.Opti()

theta = opti.variable(
    init_guess=theta_0,
    n_vars=N,
)
H = opti.variable(
    H_0,
    n_vars=N,
)

Re_theta = ue * theta / nu

H_star = np.where(H < 4, 1.515 + 0.076 * (H - 4)**2 / H,
                  1.515 + 0.040 * (H - 4)**2 / H)  # From AVF Eq. 4.53
c_f = 2 / Re_theta * np.where(H < 6.2, -0.066 + 0.066 * np.abs(6.2 - H)**1.5 /
                              (H - 1), -0.066 + 0.066 * (H - 6.2)**2 /
                              (H - 4)**2)  # From AVF Eq. 4.54
c_D = H_star / 2 / Re_theta * np.where(
    H < 4, 0.207 + 0.00205 * np.abs(4 - H)**5.5, 0.207 - 0.100 *
    (H - 4)**2 / H**2)  # From AVF Eq. 4.55
Re_theta_o = 10**(2.492 / (H - 1)**0.43 + 0.7 *
                  (np.tanh(14 / (H - 1) - 9.24) + 1))  # From AVF Eq. 6.38

d_theta_dx = np.diff(theta) / np.diff(x)
d_ue_dx = np.diff(ue) / np.diff(x)
d_H_star_dx = np.diff(H_star) / np.diff(x)


def int(x):
    return (x[1:] + x[:-1]) / 2