コード例 #1
0
ファイル: aerodynamics.py プロジェクト: cohen39/AeroSandbox
def Cd_cylinder(Re_D, subcritical_only=False):
    """
    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
    """
    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 = cas.log10(Re_D)

    if subcritical_only:
        Cd = 10**(csub0 * x + csub1) + csub2 + csub3 * x
        return Cd
    else:
        log10_Cd = (
            (cas.log10(10**(csub0 * x + csub1) + csub2 + csub3 * x)) *
            (1 - 1 / (1 + cas.exp(-csigh * (x - csigc)))) +
            (csup0 +
             csupscl / csuph * cas.log(cas.exp(csuph * (csupc - x)) + 1)) *
            (1 / (1 + cas.exp(-csigh * (x - csigc)))))
        Cd = 10**log10_Cd
        return Cd
コード例 #2
0
ファイル: aerodynamics.py プロジェクト: cohen39/AeroSandbox
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 = cas.fmax(Re_c, 1)
    log10_Re = cas.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 + cas.exp(ctr - rtr * r - atr * a - atr2 * a**2)) + (
            c0l + a1l * a + a4l * a**4) * (
                1 - 1 / (1 + cas.exp(ctr - rtr * r - atr * a - atr2 * a**2)))

    return Cl
コード例 #3
0
ファイル: aerodynamics.py プロジェクト: cohen39/AeroSandbox
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 = cas.fmax(Re_c, 1)
    log10_Re = cas.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 + cas.exp(ctr - rtr * r - atr * a - rtr2 * r**2)) + (
            c0l + a1l * a + a2l * a**2) * (
                1 - 1 / (1 + cas.exp(ctr - rtr * r - atr * a - rtr2 * r**2)))

    Cd = 10**log10_Cd

    return Cd
コード例 #4
0
ファイル: aerodynamics.py プロジェクト: cohen39/AeroSandbox
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 = cas.fmax(Re_c, 1)
    log10_Re = cas.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 + cas.exp(ctr - rtr * r - atr * a - rtr2 * r**2)) + (
            c0l + a1l * a + asl / (1 + cas.exp(-ksl * (a - xsl)))) * (
                1 - 1 / (1 + cas.exp(ctr - rtr * r - atr * a - rtr2 * r**2)))

    return Cl
コード例 #5
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 * cas.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
コード例 #6
0
ファイル: viscous.py プロジェクト: sovsep/AeroSandbox
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 = cas.fmax(Re_c, 1)
    log10_Re = cas.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 + cas.exp(kat * (a - at) + krt * (r - rt) + kart * (a - at) * (r - rt)))) + (
                       c0l + r1l * (r - 4)) * (
                       1 - 1 / (1 + cas.exp(kat * (a - at) + krt * (r - rt) + kart * (a - at) * (r - rt))))

    Cd = 10 ** log10_Cd

    return Cd
コード例 #7
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 = cas.log10(duration_mins)

    return a * duration_mins**(
        b0 + b1 * log_duration_mins + b2 * log_duration_mins**2
    )  # essentially, a cubic in log-log space
コード例 #8
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 * cas.log10(beta) + p2)

    mass = mass_lb / 2.20462262185

    return mass
コード例 #9
0
def fit(
        model,  # type: callable
        x_data,  # type: dict
        y_data,  # type: np.ndarray
        param_guesses,  # type: dict
        param_bounds=None,  # type: dict
        weights=None,  # type: np.ndarray
        verbose=True,  # type: bool
        scale_problem=True,  # type: bool
        put_residuals_in_logspace=False,  # type: bool
):
    """
    Fits a model to data through least-squares minimization.
    :param model: A callable with syntax f(x, p) where:
            x is a dict of dependent variables. Same format as x_data [dict of 1D ndarrays of length n].
            p is a dict of parameters. Same format as param_guesses [dict of scalars].
        Model should use CasADi functions for differentiability.
    :param x_data: a dict of dependent variables. Same format as model's x. [dict of 1D ndarrays of length n]
    :param y_data: independent variable. [1D ndarray of length n]
    :param param_guesses: a dict of fit parameters. Same format as model's p. Keys are parameter names, values are initial guesses. [dict of scalars]
    :param param_bounds: Optional: a dict of bounds on fit parameters.
        Keys are parameter names, values are a tuple of (min, max).
        May contain only a subset of param_guesses if desired.
        Use None to represent one-sided constraints (i.e. (None, 5)).
        [dict of tuples]
    :param weights: Optional: weights for data points. If not supplied, weights are assumed to be uniform.
        Weights are automatically normalized. [1D ndarray of length n]
    :param verbose: Whether or not to print information about parameters and goodness of fit.
    :param scale_problem: Whether or not to attempt to scale variables, constraints, and objective for more robust solve. [boolean]
    :param put_residuals_in_logspace: Whether to optimize using the logarithmic error as opposed to the absolute error (useful for minimizing percent error).
        Note: If any model outputs or data are negative, this will fail!
    :return: Optimal fit parameters [dict]
    """
    opti = cas.Opti()

    # Handle weighting
    if weights is None:
        weights = cas.GenDM_ones(y_data.shape[0])
    weights /= cas.sum1(weights)

    def fit_param(initial_guess, lower_bound=None, upper_bound=None):
        """
        Helper function to create a fit variable
        :param initial_guess:
        :param lower_bound:
        :param upper_bound:
        :return:
        """
        if scale_problem and np.abs(initial_guess) > 1e-8:
            var = initial_guess * opti.variable()  # scale variables
        else:
            var = opti.variable()
        opti.set_initial(var, initial_guess)
        if lower_bound is not None:
            lower_bound_abs = np.abs(lower_bound)
            if scale_problem and lower_bound_abs > 1e-8:
                opti.subject_to(
                    var / lower_bound_abs > lower_bound / lower_bound_abs)
            else:
                opti.subject_to(var > lower_bound)
        if upper_bound is not None:
            upper_bound_abs = np.abs(upper_bound)
            if scale_problem and upper_bound_abs > 1e-8:
                opti.subject_to(
                    var / upper_bound_abs < upper_bound / upper_bound_abs)
            else:
                opti.subject_to(var < upper_bound)
        return var

    if param_bounds is None:
        params = {k: fit_param(param_guesses[k]) for k in param_guesses}
    else:
        params = {
            k: fit_param(param_guesses[k]) if k not in param_bounds else
            fit_param(param_guesses[k], param_bounds[k][0], param_bounds[k][1])
            for k in param_guesses
        }

    if scale_problem:
        y_model_initial = model(x_data, param_guesses)
        if not put_residuals_in_logspace:
            residuals_initial = y_model_initial - y_data
        else:
            residuals_initial = cas.log10(y_model_initial) - cas.log10(y_data)
        SSE_initial = cas.sum1(weights * residuals_initial**2)

        y_model = model(x_data, params)
        if not put_residuals_in_logspace:
            residuals = y_model - y_data
        else:
            residuals = cas.log10(y_model) - cas.log10(y_data)
        SSE = cas.sum1(weights * residuals**2)
        opti.minimize(SSE / SSE_initial)

    else:
        y_model = model(x_data, params)
        if not put_residuals_in_logspace:
            residuals = y_model - y_data
        else:
            residuals = cas.log10(y_model) - cas.log10(y_data)
        SSE = cas.sum1(weights * residuals**2)
        opti.minimize(SSE)

    # Solve
    p_opts = {}
    s_opts = {}
    s_opts["max_iter"] = 3e3  # If you need to interrupt, just use ctrl+c
    # s_opts["mu_strategy"] = "adaptive"
    opti.solver('ipopt', p_opts, s_opts)
    opti.solver('ipopt')
    if verbose:
        sol = opti.solve()
    else:
        with stdout_redirected():
            sol = opti.solve()

    params_solved = {}
    for k in params:
        try:
            params_solved[k] = sol.value(params[k])
        except:
            params_solved[k] = np.NaN

    # printing
    if verbose:
        # Print parameters
        print("\nFit Parameters:")
        if len(params_solved) <= 20:
            [print("\t%s = %f" % (k, v)) for k, v in params_solved.items()]
        else:
            print("\t%i parameters solved for." % len(params_solved))
        print("\nGoodness of Fit:")

        # Print RMS error
        weighted_RMS_error = sol.value(
            cas.sqrt(cas.sum1(weights * residuals**2)))
        print("\tWeighted RMS error: %f" % weighted_RMS_error)

        # Print R^2
        y_data_mean = cas.sum1(y_data) / y_data.shape[0]
        SS_tot = cas.sum1(weights * (y_data - y_data_mean)**2)
        SS_res = cas.sum1(weights * (y_data - y_model)**2)
        R_squared = sol.value(1 - SS_res / SS_tot)
        print("\tR^2: %f" % R_squared)

    return params_solved