def test_fuselage_aerodynamics_optimization():
    opti = asb.Opti()

    alpha = opti.variable(init_guess=0, lower_bound=0, upper_bound=30)
    beta = opti.variable(init_guess=0)

    fuselage = asb.Fuselage(xsecs=[
        asb.FuselageXSec(xyz_c=[xi, 0, 0],
                         radius=asb.Airfoil("naca0010").local_thickness(
                             0.8 * xi)) for xi in np.cosspace(0, 1, 20)
    ], )

    aero = asb.AeroBuildup(airplane=asb.Airplane(fuselages=[fuselage]),
                           op_point=asb.OperatingPoint(velocity=10,
                                                       alpha=alpha,
                                                       beta=beta)).run()

    opti.minimize(-aero["L"] / aero["D"])
    sol = opti.solve(verbose=False)
    print(sol.value(alpha))
    assert sol.value(alpha) > 10 and sol.value(alpha) < 20
    assert sol.value(beta) == pytest.approx(0, abs=1e-3)

    opti.minimize(aero["D"])
    sol = opti.solve(verbose=False)
    assert sol.value(alpha) == pytest.approx(0, abs=1e-2)
    assert sol.value(beta) == pytest.approx(0, abs=1e-2)
Ejemplo n.º 2
0
                 xsecs=[
                     asb.WingXSec(
                         xyz_le=[0, 0, 0],
                         chord=0.7,
                     ),
                     asb.WingXSec(xyz_le=[0.14, 1.25, 0], chord=0.42),
                 ]),
        asb.Wing(name="V-stab",
                 xyz_le=[4, 0, 0],
                 xsecs=[
                     asb.WingXSec(
                         xyz_le=[0, 0, 0],
                         chord=0.7,
                     ),
                     asb.WingXSec(xyz_le=[0.14, 0, 1], chord=0.42)
                 ])
    ],
    fuselages=[
        asb.Fuselage(name="Fuselage",
                     xyz_le=[0, 0, 0],
                     xsecs=[
                         asb.FuselageXSec(
                             xyz_c=[xi * 5 - 0.5, 0, 0],
                             radius=asb.Airfoil("naca0024").local_thickness(
                                 x_over_c=xi)) for xi in np.cosspace(0, 1, 30)
                     ])
    ])

if __name__ == '__main__':
    airplane.draw()
Ejemplo n.º 3
0
    def repanel(
        self,
        n_points_per_side: int = 100,
    ) -> 'Airfoil':
        """
        Returns a repaneled version of the airfoil with cosine-spaced coordinates on the upper and lower surfaces.
        :param n_points_per_side: Number of points per side (upper and lower) of the airfoil [int]
            Notes: The number of points defining the final airfoil will be n_points_per_side*2-1,
            since one point (the leading edge point) is shared by both the upper and lower surfaces.
        :return: Returns the new airfoil.
        """

        upper_original_coors = self.upper_coordinates(
        )  # Note: includes leading edge point, be careful about duplicates
        lower_original_coors = self.lower_coordinates(
        )  # Note: includes leading edge point, be careful about duplicates

        # Find distances between coordinates, assuming linear interpolation
        upper_distances_between_points = (
            (upper_original_coors[:-1, 0] - upper_original_coors[1:, 0])**2 +
            (upper_original_coors[:-1, 1] - upper_original_coors[1:, 1])**
            2)**0.5
        lower_distances_between_points = (
            (lower_original_coors[:-1, 0] - lower_original_coors[1:, 0])**2 +
            (lower_original_coors[:-1, 1] - lower_original_coors[1:, 1])**
            2)**0.5
        upper_distances_from_TE = np.hstack(
            (0, np.cumsum(upper_distances_between_points)))
        lower_distances_from_LE = np.hstack(
            (0, np.cumsum(lower_distances_between_points)))
        upper_distances_from_TE_normalized = upper_distances_from_TE / upper_distances_from_TE[
            -1]
        lower_distances_from_LE_normalized = lower_distances_from_LE / lower_distances_from_LE[
            -1]

        distances_from_TE_normalized = np.hstack(
            (upper_distances_from_TE_normalized,
             1 + lower_distances_from_LE_normalized[1:]))

        # Generate a cosine-spaced list of points from 0 to 1
        cosspaced_points = np.cosspace(0, 1, n_points_per_side)
        s = np.hstack((
            cosspaced_points,
            1 + cosspaced_points[1:],
        ))

        # Check that there are no duplicate points in the airfoil.
        if np.any(np.diff(distances_from_TE_normalized) == 0):
            raise ValueError(
                "This airfoil has a duplicated point (i.e. two adjacent points with the same (x, y) coordinates), so you can't repanel it!"
            )

        x = interp1d(
            distances_from_TE_normalized,
            self.x(),
            kind="cubic",
        )(s)
        y = interp1d(
            distances_from_TE_normalized,
            self.y(),
            kind="cubic",
        )(s)

        return Airfoil(name=self.name, coordinates=stack_coordinates(x, y))
Ejemplo n.º 4
0
                    twist=0,
                    airfoil=tail_airfoil,
                    control_surface_is_symmetric=True,  # Rudder
                    control_surface_deflection=0,
                ),
                asb.WingXSec(
                    xyz_le=[0.04, 0, 0.15],
                    chord=0.06,
                    twist=0,
                    airfoil=tail_airfoil
                )
            ]
        ).translate([0.6, 0, 0.07])
    ],
    fuselages=[
        asb.Fuselage(
            name="Fuselage",
            xsecs=[
                asb.FuselageXSec(
                    xyz_c=[0.8 * xi - 0.1, 0, 0.1 * xi - 0.03],
                    radius=0.6 * asb.Airfoil("dae51").local_thickness(x_over_c=xi)
                )
                for xi in np.cosspace(0, 1, 30)
            ]
        )
    ]
)

if __name__ == '__main__':
    airplane.draw()
Ejemplo n.º 5
0
def get_kulfan_coordinates(
        lower_weights=-0.2 * np.ones(5),  # type: np.ndarray
        upper_weights=0.2 * np.ones(5),  # type: np.ndarray
        enforce_continuous_LE_radius=True,
        TE_thickness=0.,  # type: float
        n_points_per_side=_default_n_points_per_side,  # type: int
        N1=0.5,  # type: float
        N2=1.0,  # type: float
) -> np.ndarray:
    """
    Calculates the coordinates of a Kulfan (CST) airfoil.
    To make a Kulfan (CST) airfoil, use the following syntax:

    asb.Airfoil("My Airfoil Name", coordinates = asb.kulfan_coordinates(*args))

    More on Kulfan (CST) airfoils: http://brendakulfan.com/docs/CST2.pdf
    Notes on N1, N2 (shape factor) combinations:
        * 0.5, 1: Conventional airfoil
        * 0.5, 0.5: Elliptic airfoil
        * 1, 1: Biconvex airfoil
        * 0.75, 0.75: Sears-Haack body (radius distribution)
        * 0.75, 0.25: Low-drag projectile
        * 1, 0.001: Cone or wedge airfoil
        * 0.001, 0.001: Rectangle, circular duct, or circular rod.
    :param lower_weights:
    :param upper_weights:
    :param enforce_continuous_LE_radius: Enforces a continous leading-edge radius by throwing out the first lower weight.
    :param TE_thickness:
    :param n_points_per_side:
    :param N1: LE shape factor
    :param N2: TE shape factor
    :return:
    """

    if enforce_continuous_LE_radius:
        lower_weights[0] = -1 * upper_weights[0]

    x_lower = np.cosspace(0, 1, n_points_per_side)
    x_upper = x_lower[::-1]

    x_lower = x_lower[
        1:]  # Trim off the nose coordinate so there are no duplicates

    def shape(w, x):
        # Class function
        C = x**N1 * (1 - x)**N2

        # Shape function (Bernstein polynomials)
        n = len(w) - 1  # Order of Bernstein polynomials

        K = comb(n, np.arange(n + 1))  # Bernstein polynomial coefficients

        S_matrix = (w * K * np.expand_dims(x, 1)**np.arange(n + 1) *
                    np.expand_dims(1 - x, 1)**(n - np.arange(n + 1))
                    )  # Polynomial coefficient * weight matrix
        # S = np.sum(S_matrix, axis=1)
        S = np.array(
            [np.sum(S_matrix[i, :]) for i in range(S_matrix.shape[0])])

        # Calculate y output
        y = C * S
        return y

    y_lower = shape(lower_weights, x_lower)
    y_upper = shape(upper_weights, x_upper)

    # TE thickness
    y_lower -= x_lower * TE_thickness / 2
    y_upper += x_upper * TE_thickness / 2

    x = np.concatenate([x_upper, x_lower])
    y = np.concatenate([y_upper, y_lower])
    coordinates = np.vstack((x, y)).T

    return coordinates
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
import aerosandbox as asb
import aerosandbox.numpy as np

n_timesteps = 301
mass_block = 1

opti = asb.Opti()

time = np.cosspace(0, 1, n_timesteps)

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

velocity = opti.derivative_of(
    position,
    with_respect_to=time,
    derivative_init_guess=1,
)

force = opti.variable(init_guess=np.linspace(1, -1, n_timesteps),
                      n_vars=n_timesteps)

opti.constrain_derivative(
    variable=velocity,
    with_respect_to=time,
    derivative=force / mass_block,
)

effort_expended = np.sum(np.trapz(force**2) * np.diff(time))

opti.minimize(effort_expended)
def test_spacing(types):
    for x in types["scalar"]:
        np.linspace(x - 1, x + 1, 10)
        np.cosspace(x - 1, x + 1, 10)
Ejemplo n.º 9
0

def tangent(
        x_over_L: np.ndarray,
):
    return haack_series(
        x_over_L=x_over_L,
        C=2 / 3
    )


if __name__ == '__main__':
    import matplotlib.pyplot as plt
    import aerosandbox.tools.pretty_plots as p

    fig, ax = plt.subplots()

    x_over_L = np.cosspace(0, 1, 2000)
    FR = 3

    plt.plot(x_over_L * FR, karman(x_over_L), label="$C=0$ (LD-Haack, Karman)")
    plt.plot(x_over_L * FR, LV_haack(x_over_L), label="$C=1/3$ (LV-Haack)")
    plt.plot(x_over_L * FR, tangent(x_over_L), label="$C=2/3$ (Tangent)")

    p.equal()
    p.show_plot(
        f"Nosecone Haack Series\nFineness Ratio $FR = {FR}$",
        "Length $x$",
        "Radius $r$"
    )
Ejemplo n.º 10
0
import aerosandbox as asb
import aerosandbox.numpy as np

import matplotlib.pyplot as plt
import aerosandbox.tools.pretty_plots as p

fuselage = asb.Fuselage(xsecs=[
    asb.FuselageXSec(xyz_c=[xi, 0, 0],
                     radius=asb.Airfoil("naca0020").local_thickness(xi) / 2)
    for xi in np.cosspace(0, 1, 20)
], )

fig, ax = plt.subplots(figsize=(7, 6))
V = np.linspace(10, 1000, 1001)
op_point = asb.OperatingPoint(velocity=V, )

aero = asb.AeroBuildup(
    airplane=asb.Airplane(fuselages=[fuselage]),
    op_point=op_point,
).run()

plt.plot(op_point.mach(), aero["CD"], label="Full Model")

aero = asb.AeroBuildup(airplane=asb.Airplane(fuselages=[fuselage]),
                       op_point=op_point,
                       include_wave_drag=False).run()

plt.plot(op_point.mach(),
         aero["CD"],
         zorder=1.9,
         label="Model without Wave Drag")