Ejemplo n.º 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
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
Ejemplo n.º 3
0
def eng_string(x: float, unit: str = None, format='%.3g', si=True) -> str:
    '''
    Taken from: https://stackoverflow.com/questions/17973278/python-decimal-engineering-notation-for-mili-10e-3-and-micro-10e-6/40691220

    Returns float/int value <x> formatted in a simplified engineering format -
    using an exponent that is a multiple of 3.

    Args:

        x: The value to be formatted. Float or int.

        unit: A unit of the quantity to be expressed, given as a string. Example: Newtons -> "N"

        format: A printf-style string used to format the value before the exponent.

        si: if true, use SI suffix for exponent. (k instead of e3, n instead of
            e-9, etc.)

    Examples:

    With format='%.2f':
        1.23e-08 -> 12.30e-9
             123 -> 123.00
          1230.0 -> 1.23e3
      -1230000.0 -> -1.23e6

    With si=True:
          1230.0 -> "1.23k"
      -1230000.0 -> "-1.23M"

    With unit="N" and si=True:
          1230.0 -> "1.23 kN"
      -1230000.0 -> "-1.23 MN"
    '''
    sign = ''
    if x < 0:
        x = -x
        sign = '-'
    elif x == 0:
        return format % 0
    elif np.isnan(x):
        return "NaN"
    exp = int(np.floor(np.log10(x)))
    exp3 = exp - (exp % 3)
    x3 = x / (10**exp3)

    if si and exp3 >= -24 and exp3 <= 24 and exp3 != 0:
        exp3_text = 'yzafpnμm kMGTPEZY'[(exp3 + 24) // 3]
    elif exp3 == 0:
        exp3_text = ''
    else:
        exp3_text = f'e{exp3}'

    if unit is not None:
        if si:
            exp3_text = " " + exp3_text + unit
        else:
            exp3_text = exp3_text + " " + unit

    return ('%s' + format + '%s') % (sign, x3, exp3_text)
Ejemplo n.º 4
0
def Cd_profile_e216(alpha, Re_c):
    # A curve fit I did to a Eppler 216 (e216) airfoil, 2D XFoil data. Incompressible flow.
    # Within -2 < alpha < 12 and 10^4 < Re_c < 10^6, has R^2 = 0.9995
    # Likely valid from -6 < alpha < 12 and 10^4 < Re_c < 10^6.
    # see: C:\Projects\GitHub\firefly_aerodynamics\Gists and Ideas\XFoil Drag Fitting\e216

    Re_c = np.fmax(Re_c, 1)
    log10_Re = np.log10(Re_c)

    # Coeffs
    a1l = 4.7167470806940448e-02
    a1t = 7.5663005080888857e-02
    a2l = 8.7552076545610764e-04
    a4t = 1.1220763679805319e-05
    atr = 4.2456038382581129e-01
    c0l = -1.4099657419753771e+00
    c0t = -2.3855286371940609e+00
    ctr = 9.1474872611212135e+01
    rtr = 3.0218483612170434e+01
    rtr2 = -2.4515094313899279e+00

    a = alpha
    r = log10_Re

    log10_Cd = (c0t + a1t * a + a4t * a**4) * 1 / (
        1 + np.exp(ctr - rtr * r - atr * a - rtr2 * r**2)) + (
            c0l + a1l * a +
            a2l * a**2) * (1 - 1 /
                           (1 + np.exp(ctr - rtr * r - atr * a - rtr2 * r**2)))

    Cd = 10**log10_Cd

    return Cd
Ejemplo n.º 5
0
def Cl_rae2822(alpha, Re_c):
    # A curve fit I did to a RAE2822 airfoil, 2D XFoil data. Incompressible flow.
    # Within -2 < alpha < 12 and 10^4 < Re_c < 10^6, has R^2 = 0.9857
    # Likely valid from -6 < alpha < 12 and 10^4 < Re_c < 10^6.
    # See: C:\Projects\GitHub\firefly_aerodynamics\Gists and Ideas\XFoil Drag Fitting\rae2822

    Re_c = np.fmax(Re_c, 1)
    log10_Re = np.log10(Re_c)

    # Coeffs
    a1l = 5.5686866813855172e-02
    a1t = 9.7472055628494134e-02
    a4l = -7.2145733312046152e-09
    a4t = -3.6886704372829236e-06
    atr = 8.3723547264375520e-01
    atr2 = -8.3128119739031697e-02
    c0l = -4.9103908291438701e-02
    c0t = 2.3903424824298553e-01
    ctr = 1.3082854754897108e+01
    rtr = 2.6963082864300731e+00

    a = alpha
    r = log10_Re

    Cl = (c0t + a1t * a + a4t * a**4) * 1 / (
        1 + np.exp(ctr - rtr * r - atr * a - atr2 * a**2)) + (
            c0l + a1l * a +
            a4l * a**4) * (1 - 1 /
                           (1 + np.exp(ctr - rtr * r - atr * a - atr2 * a**2)))

    return Cl
Ejemplo n.º 6
0
def mass_motor_electric(
        max_power,
        kv_rpm_volt=1000,  # This is in rpm/volt, not rads/sec/volt!
        voltage=20,
        method="astroflight"):
    """
    Estimates the mass of a brushless DC electric motor.
    Curve fit to scraped Hobbyking BLDC motor data as of 2/24/2020.
    Estimated range of validity: 50 < max_power < 10000
    :param max_power: maximum power [W]
    :param kv: Voltage constant of the motor, measured in rpm/volt, not rads/sec/volt! [rpm/volt]
    :param voltage: Operating voltage of the motor [V]
    :param method: method to use. "burton", "hobbyking", or "astroflight" (increasing level of detail).
    Burton source: https://dspace.mit.edu/handle/1721.1/112414
    Hobbyking source: C:\Projects\GitHub\MotorScraper, https://github.com/austinstover/MotorScraper
    Astroflight source: Gates, et. al., "Combined Trajectory, Propulsion, and Battery Mass Optimization for Solar-Regen..."
        https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=3932&context=facpub
        Validity claimed from 1.5 kW to 15 kW, kv from 32 to 1355.
    :return: estimated motor mass [kg]
    """
    if method == "burton":
        return max_power / 4128  # Less sophisticated model. 95% CI (3992, 4263), R^2 = 0.866
    elif method == "hobbyking":
        return 10**(0.8205 * np.log10(max_power) - 3.155
                    )  # More sophisticated model
    elif method == "astroflight":
        max_current = max_power / voltage
        return 2.464 * max_current / kv_rpm_volt + 0.368  # Even more sophisticated model
Ejemplo n.º 7
0
def Cl_e216(alpha, Re_c):
    # A curve fit I did to a Eppler 216 (e216) airfoil, 2D XFoil data. Incompressible flow.
    # Within -2 < alpha < 12 and 10^4 < Re_c < 10^6, has R^2 = 0.9994
    # Likely valid from -6 < alpha < 12 and 10^4 < Re_c < Inf.
    # See: C:\Projects\GitHub\firefly_aerodynamics\Gists and Ideas\XFoil Drag Fitting\e216

    Re_c = np.fmax(Re_c, 1)
    log10_Re = np.log10(Re_c)

    # Coeffs
    a1l = 3.0904412662858878e-02
    a1t = 9.6452654383488254e-02
    a4t = -2.5633334023068302e-05
    asl = 6.4175433185427011e-01
    atr = 3.6775107602844948e-01
    c0l = -2.5909363461176749e-01
    c0t = 8.3824440586718862e-01
    ctr = 1.1431810545735890e+02
    ksl = 5.3416670116733611e-01
    rtr = 3.9713338634462829e+01
    rtr2 = -3.3634858542657771e+00
    xsl = -1.2220899840236835e-01

    a = alpha
    r = log10_Re

    Cl = (c0t + a1t * a + a4t * a**4) * 1 / (
        1 + np.exp(ctr - rtr * r - atr * a - rtr2 * r**2)) + (
            c0l + a1l * a + asl / (1 + np.exp(-ksl * (a - xsl)))) * (
                1 - 1 / (1 + np.exp(ctr - rtr * r - atr * a - rtr2 * r**2)))

    return Cl
Ejemplo n.º 8
0
def Cd_profile_rae2822(alpha, Re_c):
    # A curve fit I did to a RAE2822 airfoil, 2D XFoil data. Incompressible flow.
    # Within -2 < alpha < 12 and 10^4 < Re_c < 10^6, has R^2 = 0.9995
    # Likely valid from -6 < alpha < 12 and 10^4 < Re_c < Inf.
    # see: C:\Projects\GitHub\firefly_aerodynamics\Gists and Ideas\XFoil Drag Fitting\e216

    Re_c = np.fmax(Re_c, 1)
    log10_Re = np.log10(Re_c)

    # Coeffs
    at = 8.1034027621509015e+00
    c0l = -8.4296746456429639e-01
    c0t = -1.3700609138855402e+00
    kart = -4.1609994062600880e-01
    kat = 5.9510959342452441e-01
    krt = -7.1938030052506197e-01
    r1l = 1.1548628822014631e-01
    r1t = -4.9133662875044504e-01
    rt = 5.0070459892411696e+00

    a = alpha
    r = log10_Re

    log10_Cd = (c0t + r1t * (r - 4)) * (
            1 / (1 + np.exp(kat * (a - at) + krt * (r - rt) + kart * (a - at) * (r - rt)))) + (
                       c0l + r1l * (r - 4)) * (
                       1 - 1 / (1 + np.exp(kat * (a - at) + krt * (r - rt) + kart * (a - at) * (r - rt))))

    Cd = 10 ** log10_Cd

    return Cd
Ejemplo n.º 9
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
Ejemplo n.º 10
0
def power_human(
        duration,  # type: float
        dataset="Healthy Men"  # type: str
):
    """
    Finds the power output that a human can sustain for a given duration.
    Data was fit for durations in the range of 6 seconds to 60,000 seconds.
    Fits are modeled at: AeroSandbox/studies/HumanPower
    Data Source: Bicycling Science by D. Wilson, 2004. Figure 2.4.
        Wilson is aggregating many data sources here.
        The raw data pulls from a variety of sources:
            * NASA SP-3006, 1964
            * U.K. amateur trials and time-trials records (Whitt, F.R. 1971 "A note on the estimation of the energy expenditure of sporting cyclists." Ergonomics 14)
            * Wilsons' own analyses
    Weight estimates for tests subjects are unfortunately not given.
    :param duration: Time to sustain power output [seconds]
    :param dataset: Dataset to pull from. A string that is one of the following:
        "Healthy Men",
        "First-Class Athletes",
        "World-Class Athletes",
    :return: Sustainable power output for the specified duration [W]
    """
    if dataset == "Healthy Men":
        a = 373.153360
        b0 = -0.173127
        b1 = 0.083282
        b2 = -0.042785
    elif dataset == "First-Class Athletes":
        a = 502.332185
        b0 = -0.179030
        b1 = 0.097926
        b2 = -0.024855
    elif dataset == "World-Class Athletes":
        a = 869.963370
        b0 = -0.234291
        b1 = 0.064395
        b2 = -0.009197
    else:
        raise ValueError("Bad value of 'dataset'!")

    duration_mins = duration / 60
    log_duration_mins = np.log10(duration_mins)

    return a * duration_mins**(
        b0 + b1 * log_duration_mins + b2 * log_duration_mins**2
    )  # essentially, a cubic in log-log space
Ejemplo n.º 11
0
def mass_gearbox(
    power,
    rpm_in,
    rpm_out,
):
    """
    Estimates the mass of a gearbox.

    Based on data from NASA/TM-2009-215680, available here:
        https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20090042817.pdf

    R^2 = 0.92 to the data.

    To quote this document:
        "The correlation was developed based on actual weight
        data from over fifty rotorcrafts, tiltrotors, and turboprop
        aircraft."

    Data fits in the NASA document were thrown out and refitted to extrapolate more sensibly; see:
        C:\Projects\GitHub\AeroSandbox\studies\GearboxMassFits
    :param power: Shaft power through the gearbox [W]
    :param rpm_in: RPM of the input to the gearbox [rpm]
    :param rpm_out: RPM of the output of the gearbox [rpm]
    :return: Estimated mass of the gearbox [kg]
    """
    power_hp = power / 745.7

    beta = (power_hp / rpm_out)**0.75 * (rpm_in / rpm_out)**0.15
    # Beta is a parametric value that tends to collapse the masses of gearboxes onto a line.
    # Data fit is considered tightly valid for gearboxes with 1 < beta < 100. Sensible extrapolations are made beyond that.

    p1 = 1.0445171124733774
    p2 = 2.0083615496306910

    mass_lb = 10**(p1 * np.log10(beta) + p2)

    mass = mass_lb / 2.20462262185

    return mass
Ejemplo n.º 12
0
    def generate_polars(
        self,
        alphas=np.linspace(-15, 15, 21),
        Res=np.geomspace(1e4, 1e7, 10),
        cache_filename: str = None,
        xfoil_kwargs: Dict[str, Any] = None,
        unstructured_interpolated_model_kwargs: Dict[str, Any] = None,
    ) -> None:
        """
        Generates airfoil polars (CL, CD, CM functions) and assigns them in-place to this Airfoil's polar functions.

        In other words, when this function is run, the following functions will be added (or overwritten) to the instance:
            * Airfoil.CL_function(alpha, Re, mach, deflection)
            * Airfoil.CD_function(alpha, Re, mach, deflection)
            * Airfoil.CM_function(alpha, Re, mach, deflection)

        Where alpha is in degrees. Right now, deflection is not used.

        Args:

            alphas: The range of alphas to sample from XFoil at.

            Res: The range of Reynolds numbers to sample from XFoil at.

            cache_filename: A path-like filename (ideally a "*.json" file) that can be used to cache the XFoil
            results, making it much faster to regenerate the results.

            xfoil_kwargs: Keyword arguments to pass into the AeroSandbox XFoil module. See the aerosandbox.XFoil
            constructor for options.

            unstructured_interpolated_model_kwargs: Keyword arguments to pass into the UnstructuredInterpolatedModels
            that contain the polars themselves. See the aerosandbox.UnstructuredInterpolatedModel constructor for
            options.

        Warning: In-place operation! Modifies this Airfoil object by setting Airfoil.CL_function, etc. to the new
        polars.

        Returns: None (in-place)

        """
        if self.coordinates is None:
            raise ValueError(
                "Cannot generate polars for an airfoil that you don't have the coordinates of!"
            )

        ### Set defaults
        if xfoil_kwargs is None:
            xfoil_kwargs = {}
        if unstructured_interpolated_model_kwargs is None:
            unstructured_interpolated_model_kwargs = {}

        xfoil_kwargs = {  # See asb.XFoil for documentation on these.
            "verbose": False,
            "max_iter": 20,
            "xfoil_repanel": True,
            **xfoil_kwargs
        }

        unstructured_interpolated_model_kwargs = {  # These were tuned heuristically as defaults!
            "resampling_interpolator_kwargs": {
                "degree": 0,
                # "kernel": "linear",
                "kernel": "multiquadric",
                "epsilon": 3,
                "smoothing": 0.01,
                # "kernel": "cubic"
            },
            **unstructured_interpolated_model_kwargs
        }

        ### Retrieve XFoil Polar Data from cache, if it exists.
        data = None
        if cache_filename is not None:
            try:
                with open(cache_filename, "r") as f:
                    data = {k: np.array(v) for k, v in json.load(f).items()}
            except FileNotFoundError:
                pass

        ### Analyze airfoil with XFoil, if needed
        if data is None:

            from aerosandbox.aerodynamics.aero_2D import XFoil

            def get_run_data(
                Re
            ):  # Get the data for an XFoil alpha sweep at one specific Re.
                run_data = XFoil(airfoil=self, Re=Re,
                                 **xfoil_kwargs).alpha(alphas)
                run_data["Re"] = Re * np.ones_like(run_data["alpha"])
                return run_data  # Data is a dict where keys are figures of merit [str] and values are 1D ndarrays.

            from tqdm import tqdm

            run_datas = [  # Get a list of dicts, where each dict is the result of an XFoil run at a particular Re.
                get_run_data(Re) for Re in tqdm(
                    Res,
                    desc=
                    f"Running XFoil to generate polars for Airfoil '{self.name}':",
                )
            ]
            data = {  # Merge the dicts into one big database of all runs.
                k:
                np.concatenate(tuple([run_data[k] for run_data in run_datas]))
                for k in run_datas[0].keys()
            }

            if cache_filename is not None:  # Cache the accumulated data for later use, if it doesn't already exist.
                with open(cache_filename, "w+") as f:
                    json.dump({k: v.tolist()
                               for k, v in data.items()},
                              f,
                              indent=4)

        ### Save the raw data as an instance attribute for later use
        self.xfoil_data = data

        ### Make the interpolators for attached aerodynamics
        from aerosandbox.modeling import UnstructuredInterpolatedModel

        alpha_resample = np.concatenate(
            [
                np.array([-180, -150, -120, -90, -60, -30]), alphas[::2],
                np.array([30, 60, 90, 120, 150, 180])
            ]
        )  # This is the list of points that we're going to resample from the XFoil runs for our InterpolatedModel, using an RBF.
        Re_resample = np.concatenate(
            [
                np.array([1e0, 1e1, 1e2, 1e3]), Res,
                np.array([1e8, 1e9, 1e10, 1e11, 1e12])
            ]
        )  # This is the list of points that we're going to resample from the XFoil runs for our InterpolatedModel, using an RBF.

        x_data = {
            "alpha": data["alpha"],
            "ln_Re": np.log(data["Re"]),
        }
        x_data_resample = {
            "alpha": alpha_resample,
            "ln_Re": np.log(Re_resample)
        }

        CL_attached_interpolator = UnstructuredInterpolatedModel(
            x_data=x_data,
            y_data=data["CL"],
            x_data_resample=x_data_resample,
            **unstructured_interpolated_model_kwargs)
        log10_CD_attached_interpolator = UnstructuredInterpolatedModel(
            x_data=x_data,
            y_data=np.log10(data["CD"]),
            x_data_resample=x_data_resample,
            **unstructured_interpolated_model_kwargs)
        CM_attached_interpolator = UnstructuredInterpolatedModel(
            x_data=x_data,
            y_data=data["CM"],
            x_data_resample=x_data_resample,
            **unstructured_interpolated_model_kwargs)

        ### Determine if separated
        alpha_stall_positive = np.max(data["alpha"])  # Across all Re
        alpha_stall_negative = np.min(data["alpha"])  # Across all Re

        def separation_parameter(alpha, Re=0):
            """
            Positive if separated, negative if attached.

            This will be an input to a tanh() sigmoid blend via asb.numpy.blend(), so a value of 1 means the flow is
            ~90% separated, and a value of -1 means the flow is ~90% attached.
            """
            return 0.5 * np.softmax(alpha - alpha_stall_positive,
                                    alpha_stall_negative - alpha)

        ### Make the interpolators for separated aerodynamics
        from aerosandbox.aerodynamics.aero_2D.airfoil_polar_functions import airfoil_coefficients_post_stall

        CL_if_separated, CD_if_separated, CM_if_separated = airfoil_coefficients_post_stall(
            airfoil=self, alpha=alpha_resample)

        CD_if_separated = CD_if_separated + np.median(data["CD"])
        # The line above effectively ensures that separated CD will never be less than attached CD. Not exactly, but generally close. A good heuristic.

        CL_separated_interpolator = UnstructuredInterpolatedModel(
            x_data=alpha_resample, y_data=CL_if_separated)
        log10_CD_separated_interpolator = UnstructuredInterpolatedModel(
            x_data=alpha_resample, y_data=np.log10(CD_if_separated))
        CM_separated_interpolator = UnstructuredInterpolatedModel(
            x_data=alpha_resample, y_data=CM_if_separated)

        def CL_function(alpha, Re, mach=0, deflection=0):
            alpha = np.mod(alpha + 180,
                           360) - 180  # Keep alpha in the valid range.
            CL_attached = CL_attached_interpolator({
                "alpha": alpha,
                "ln_Re": np.log(Re),
            })
            CL_separated = CL_separated_interpolator(alpha)
            return np.blend(separation_parameter(alpha, Re), CL_separated,
                            CL_attached)

        def CD_function(alpha, Re, mach=0, deflection=0):
            alpha = np.mod(alpha + 180,
                           360) - 180  # Keep alpha in the valid range.
            log10_CD_attached = log10_CD_attached_interpolator({
                "alpha":
                alpha,
                "ln_Re":
                np.log(Re),
            })
            log10_CD_separated = log10_CD_separated_interpolator(alpha)
            return 10**np.blend(
                separation_parameter(alpha, Re),
                log10_CD_separated,
                log10_CD_attached,
            )

        def CM_function(alpha, Re, mach=0, deflection=0):
            alpha = np.mod(alpha + 180,
                           360) - 180  # Keep alpha in the valid range.
            CM_attached = CM_attached_interpolator({
                "alpha": alpha,
                "ln_Re": np.log(Re),
            })
            CM_separated = CM_separated_interpolator(alpha)
            return np.blend(separation_parameter(alpha, Re), CM_separated,
                            CM_attached)

        self.CL_function = CL_function
        self.CD_function = CD_function
        self.CM_function = CM_function
Ejemplo n.º 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