Esempio n. 1
0
def plot_Cf_flat_plates():
    from aerosandbox.tools.pretty_plots import plt, show_plot

    Res = np.geomspace(1e3, 1e8, 500)
    for method in [
            "blasius",
            "turbulent",
            "hybrid-cengel",
            "hybrid-schlichting",
            "hybrid-sharpe-convex",
            "hybrid-sharpe-nonconvex",
    ]:
        plt.loglog(Res, aero.Cf_flat_plate(Res, method=method), label=method)
    plt.ylim(1e-3, 1e-1)
    show_plot(
        "Models for Mean Skin Friction Coefficient of Flat Plate",
        r"$Re$",
        r"$C_f$",
    )
Esempio n. 2
0
def plot_Cf_flat_plates():
    sns.set(palette=sns.color_palette("husl"))
    fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8), dpi=200)
    Res = np.geomspace(1e3, 1e8, 500)
    for method in [
        "blasius",
        "turbulent",
        "hybrid-cengel",
        "hybrid-schlichting",
        "hybrid-sharpe-convex",
        "hybrid-sharpe-nonconvex",
    ]:
        plt.loglog(
            Res,
            aero.Cf_flat_plate(Res, method=method),
            label=method
        )
    plt.xlabel(r"$Re$")
    plt.ylabel(r"$C_f$")
    plt.ylim(1e-3, 1e-1)
    plt.title(r"Models for Mean Skin Friction Coefficient of Flat Plate")
    plt.tight_layout()
    plt.legend()
    plt.show()
import aerosandbox as asb
import aerosandbox.numpy as np

if __name__ == '__main__':
    af = asb.Airfoil("dae11")
    af.generate_polars()

    alpha = np.linspace(-40, 40, 300)
    re = np.geomspace(1e4, 1e12, 100)
    Alpha, Re = np.meshgrid(alpha, re)
    af.CL_function(alpha=0, Re=1e6)

    CL = af.CL_function(Alpha.flatten(), Re.flatten()).reshape(Alpha.shape)
    CD = af.CD_function(Alpha.flatten(), Re.flatten()).reshape(Alpha.shape)
    CM = af.CM_function(Alpha.flatten(), Re.flatten()).reshape(Alpha.shape)

    ##### Plot alpha-Re contours
    from aerosandbox.tools.pretty_plots import plt, show_plot, contour

    fig, ax = plt.subplots()
    contour(Alpha, Re, CL, levels=30, colorbar_label=r"$C_L$")
    plt.scatter(af.xfoil_data["alpha"],
                af.xfoil_data["Re"],
                color="k",
                alpha=0.2)
    plt.yscale('log')
    show_plot(
        f"Auto-generated Polar for {af.name} Airfoil",
        "Angle of Attack [deg]",
        "Reynolds Number [-]",
    )
Esempio n. 4
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
Esempio n. 5
0
    5e3,
    10e3,
    13e3,
    18e3,
    22e3,
    30e3,
    34e3,
    45e3,
    49e3,
    53e3,
    69e3,
    73e3,
    77e3,
    83e3,
    87e3,
] + list(87e3 + np.geomspace(5e3, 2000e3, 11)) +
                                list(0 - np.geomspace(5e3, 5000e3, 11)))

altitude_knot_points = np.sort(np.unique(altitude_knot_points))

temperature_knot_points = temperature_isa(altitude_knot_points)
pressure_knot_points = pressure_isa(altitude_knot_points)

# creates interpolated model for temperature and pressure
interpolated_temperature = InterpolatedModel(
    x_data_coordinates=altitude_knot_points,
    y_data_structured=temperature_knot_points,
)
interpolated_log_pressure = InterpolatedModel(
    x_data_coordinates=altitude_knot_points,
    y_data_structured=np.log(pressure_knot_points),
Esempio n. 6
0
def contour(
        X: np.ndarray,
        Y: np.ndarray,
        Z: np.ndarray,
        levels: Union[int, List, np.ndarray] = 31,
        colorbar: bool = True,
        linelabels: bool = True,
        cmap=mpl.cm.get_cmap('viridis'),
        alpha: float = 0.7,
        extend: str = "both",
        linecolor="k",
        linewidths: float = 0.5,
        extendrect: bool = True,
        linelabels_format: Union[str, Callable[[float], str]] = eng_string,
        linelabels_fontsize: float = 8,
        colorbar_label: str = None,
        z_log_scale: bool = False,
        contour_kwargs: Dict = None,
        contourf_kwargs: Dict = None,
        colorbar_kwargs: Dict = None,
        linelabels_kwargs: Dict = None,
        **kwargs,
):
    """
    An analogue for plt.contour and plt.tricontour and friends that produces a much prettier default graph.

    Can take inputs with either contour or tricontour syntax.

    See syntax here:
        https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html
        https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contourf.html
        https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.tricontour.html
        https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.tricontourf.html

    Args:
        X: See contour docs.

        Y: See contour docs.

        Z: See contour docs.

        levels: See contour docs.

        colorbar: Should we draw a colorbar?

        linelabels: Should we add line labels?

        cmap: What colormap should we use?

        alpha: What transparency should all plot elements be?

        extend: See contour docs.

        linecolor: What color should the line labels be?

        linewidths: See contour docs.

        extendrect: See colorbar docs.

        linelabels_format: See ax.clabel docs.

        linelabels_fontsize: See ax.clabel docs.

        contour_kwargs: Additional keyword arguments for contour.

        contourf_kwargs: Additional keyword arguments for contourf.

        colorbar_kwargs: Additional keyword arguments for colorbar.

        linelabels_kwargs: Additional keyword arguments for the line labels (ax.clabel).

        **kwargs: Additional keywords, which are passed to both contour and contourf.


    Returns: A tuple of (contour, contourf, colorbar) objects.

    """

    if contour_kwargs is None:
        contour_kwargs = {}
    if contourf_kwargs is None:
        contourf_kwargs = {}
    if colorbar_kwargs is None:
        colorbar_kwargs = {}
    if linelabels_kwargs is None:
        linelabels_kwargs = {}

    args = [
        X,
        Y,
        Z,
    ]

    shared_kwargs = kwargs
    if levels is not None:
        shared_kwargs["levels"] = levels
    if alpha is not None:
        shared_kwargs["alpha"] = alpha
    if extend is not None:
        shared_kwargs["extend"] = extend
    if z_log_scale:

        shared_kwargs = {
            "norm"   : mpl.colors.LogNorm(),
            "locator": mpl.ticker.LogLocator(
                subs=np.geomspace(1, 10, 4 + 1)[:-1]
            ),
            **shared_kwargs
        }

        if np.min(Z) <= 0:
            import warnings
            warnings.warn(
                "Warning: All values of the `Z` input to `contour()` should be nonnegative if `z_log_scale` is True!",
                stacklevel=2
            )
            Z = np.maximum(Z, 1e-300)  # Make all values nonnegative

    if colorbar_label is not None:
        colorbar_kwargs["label"] = colorbar_label

    contour_kwargs = {
        "colors"    : linecolor,
        "linewidths": linewidths,
        **shared_kwargs,
        **contour_kwargs
    }
    contourf_kwargs = {
        "cmap": cmap,
        **shared_kwargs,
        **contourf_kwargs
    }

    colorbar_kwargs = {
        "extendrect": extendrect,
        **colorbar_kwargs
    }

    linelabels_kwargs = {
        "inline"  : 1,
        "fontsize": linelabels_fontsize,
        "fmt"     : linelabels_format,
        **linelabels_kwargs
    }

    try:
        cont = plt.contour(*args, **contour_kwargs)
        contf = plt.contourf(*args, **contourf_kwargs)
    except TypeError as e:
        try:
            cont = plt.tricontour(*args, **contour_kwargs)
            contf = plt.tricontourf(*args, **contourf_kwargs)
        except TypeError:
            raise e

    if colorbar:
        cbar = plt.colorbar(**colorbar_kwargs)

        if z_log_scale:
            cbar.ax.yaxis.set_major_locator(mpl.ticker.LogLocator())
            cbar.ax.yaxis.set_major_formatter(mpl.ticker.LogFormatter())
    else:
        cbar = None

    if linelabels:
        plt.gca().clabel(cont, **linelabels_kwargs)

    return cont, contf, cbar
Esempio n. 7
0
import os, sys
from pathlib import Path

this_dir = Path(__file__).parent
sys.path.insert(0, str(this_dir.parent))
from bike import Bike
import aerosandbox.numpy as np
from aerosandbox.tools.pretty_plots import plt, show_plot, set_ticks
from scipy import optimize

speed = 24 / 2.24

fig, ax = plt.subplots()
t = np.linspace(0, 10, 500)

gear_ratios = np.geomspace(0.020 / 0.700, 0.700 / 0.700, 300)


def get_efficiency(gear_ratio):
    bike = Bike(gear_ratio=gear_ratio)
    try:
        perf = bike.steady_state_performance(speed=speed)
    except ValueError:
        return np.NaN

    return perf['motor state']['efficiency']


eff = np.array([get_efficiency(gear_ratio) for gear_ratio in gear_ratios])

plt.plot(gear_ratios, eff * 100)