示例#1
0
    def __call__(self, x):
        if isinstance(self.x_data_coordinates, dict):
            x = np.stack(
                tuple(x[k] for k, v in self.x_data_coordinates.items()))

        return np.interpn(
            points=self.x_data_coordinates_values,
            values=self.y_data_structured,
            xi=x,
            method=self.method,
            bounds_error=
            False,  # Can't be set true if general MX-type inputs are to be expected.
            fill_value=self.fill_value)
    def compute_rotation_velocity_geometry_axes(self, points):
        # Computes the effective velocity-due-to-rotation at a set of points.
        # Input: a Nx3 array of points
        # Output: a Nx3 array of effective velocities
        angular_velocity_vector_geometry_axes = np.array(
            [-self.p, self.q,
             -self.r])  # signs convert from body axes to geometry axes

        a = angular_velocity_vector_geometry_axes
        b = points

        rotation_velocity_geometry_axes = np.stack([
            a[1] * b[:, 2] - a[2] * b[:, 1], a[2] * b[:, 0] - a[0] * b[:, 2],
            a[0] * b[:, 1] - a[1] * b[:, 0]
        ],
                                                   axis=1)

        rotation_velocity_geometry_axes = -rotation_velocity_geometry_axes  # negative sign, since we care about the velocity the WING SEES, not the velocity of the wing.

        return rotation_velocity_geometry_axes
示例#3
0
    def __call__(self, x):
        if isinstance(self.x_data_coordinates, dict):
            def get_shape(value):
                try:
                    return value.shape
                except AttributeError:
                    return tuple()

            shape = np.broadcast_shapes(
                *[get_shape(v) for v in x.values()]
            )
            shape_for_reshaping = (int(np.product(shape)),)

            def reshape(value):
                try:
                    return np.reshape(value, shape_for_reshaping)
                except ValueError:
                    if isinstance(value, int) or isinstance(value, float) or value.shape == tuple() or np.product(
                            value.shape) == 1:
                        return value * np.ones(shape_for_reshaping)
                raise ValueError("Could not reshape value of one of the inputs!")

            x = np.stack(tuple(
                reshape(x[k])
                for k, v in self.x_data_coordinates.items()
            ))

        output = np.interpn(
            points=self.x_data_coordinates_values,
            values=self.y_data_structured,
            xi=x,
            method=self.method,
            bounds_error=False,  # Can't be set true if general MX-type inputs are to be expected.
            fill_value=self.fill_value
        )
        try:
            return np.reshape(output, shape)
        except UnboundLocalError:
            return output
示例#4
0
    def __init__(self,
                 x_data: Union[np.ndarray, Dict[str, np.ndarray]],
                 y_data: np.ndarray,
                 x_data_resample: Union[int, Dict[str, Union[int, np.ndarray]]] = 10,
                 resampling_interpolator: object = interpolate.RBFInterpolator,
                 resampling_interpolator_kwargs: Dict[str, Any] = None,
                 fill_value=np.NaN,  # Default behavior: return NaN for all inputs outside data range.
                 interpolated_model_kwargs: Dict[str, Any] = None,
                 ):
        """
        Creates the interpolator. Note that data must be unstructured (i.e., point cloud) for general N-dimensional
        interpolation.

        Note that if data is either 1D or structured,

        Args:

            x_data: Values of the dependent variable(s) in the dataset to be fitted. This is a dictionary; syntax is {
            var_name:var_data}.

                * If the model is one-dimensional (e.g. f(x1) instead of f(x1, x2, x3...)), you can instead supply x_data
                as a 1D ndarray. (If you do this, just treat `x` as an array in your model, not a dict.)

            y_data: Values of the independent variable in the dataset to be fitted. [1D ndarray of length n]

            x_data_resample: A parameter that guides how the x_data should be resampled onto a structured grid.

                * If this is an int, we look at each axis of the `x_data` (here, we'll call this `xi`),
                and we resample onto a linearly-spaced grid between `min(xi)` and `max(xi)` with `x_data_resample`
                points.

                * If this is a dict, it must be a dict where the keys are strings matching the keys of (the
                dictionary) `x_data`. The values can either be ints or 1D np.ndarrays.

                    * If the values are ints, then that axis is linearly spaced between `min(xi)` and `max(xi)` with
                    `x_data_resample` points.

                    * If the values are 1D np.ndarrays, then those 1D np.ndarrays are used as the resampled spacing
                    for the given axis.

            resampling_interpolator: Indicates the interpolator to use in order to resample the unstructured data
            onto a structured grid. Should be analogous to scipy.interpolate.RBFInterpolator in __init__ and __call__
            syntax. See reference here:

                * https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.RBFInterpolator.html

            resampling_interpolator_kwargs: Indicates keyword arguments (keyword-value pairs, as a dictionary) to
            pass into the resampling interpolator.

            fill_value: Gives the value that the interpolator should return for points outside of the interpolation
            domain. The interpolation domain is defined as the hypercube bounded by the coordinates specified in
            `x_data_resample`. By default, these coordinates are the tightest axis-aligned hypercube that bounds the
            point cloud data. If fill_value is None, then the interpolator will attempt to extrapolate if the interpolation method allows.

            interpolated_model_kwargs: Indicates keyword arguments to pass into the (structured) InterpolatedModel.
            Also a dictionary. See aerosandbox.InterpolatedModel for documentation on possible inputs here.

        """
        if resampling_interpolator_kwargs is None:
            resampling_interpolator_kwargs = {}
        if interpolated_model_kwargs is None:
            interpolated_model_kwargs = {}

        try:  # Try to use the InterpolatedModel initializer. If it doesn't work, then move on.
            super().__init__(
                x_data_coordinates=x_data,
                y_data_structured=y_data,
            )
            return
        except ValueError:
            pass

        # If it didn't work, this implies that x_data is multidimensional, and hence a dict-like object. Validate this.
        try:  # Determine type of `x_data`
            x_data.keys()
            x_data.values()
            x_data.items()
        except AttributeError:
            raise TypeError("`x_data` must be a dict-like object!")

        # Make the interpolator, based on x_data and y_data.
        if resampling_interpolator == interpolate.RBFInterpolator:
            resampling_interpolator_kwargs = {
                "kernel": "thin_plate_spline",
                "degree": 1,
                **resampling_interpolator_kwargs
            }

        interpolator = resampling_interpolator(
            y=np.stack(tuple(x_data.values()), axis=1),
            d=y_data,
            **resampling_interpolator_kwargs
        )

        # If x_data_resample is an int, make it into a dict that matches x_data.
        if isinstance(x_data_resample, int):
            x_data_resample = {
                k: x_data_resample
                for k in x_data.keys()
            }

        # Now, x_data_resample should be dict-like. Validate this.
        try:
            x_data_resample.keys()
            x_data_resample.values()
            x_data_resample.items()
        except AttributeError:
            raise TypeError("`x_data_resample` must be a dict-like object!")

        # Go through x_data_resample, and replace any values that are ints with linspaced arrays.
        for k, v in x_data_resample.items():
            if isinstance(v, int):
                x_data_resample[k] = np.linspace(
                    np.min(x_data[k]),
                    np.max(x_data[k]),
                    v
                )

        x_data_coordinates: Dict = x_data_resample

        x_data_structured_values = [
            xi.flatten()
            for xi in np.meshgrid(*x_data_coordinates.values(), indexing="ij")
        ]
        x_data_structured = {
            k: xi
            for k, xi in zip(x_data.keys(), x_data_structured_values)
        }

        y_data_structured = interpolator(
            np.stack(tuple(x_data_structured_values), axis=1)
        )
        y_data_structured = y_data_structured.reshape([
            np.length(xi)
            for xi in x_data_coordinates.values()
        ])

        interpolated_model_kwargs = {
            "fill_value": fill_value,
            **interpolated_model_kwargs
        }

        super().__init__(
            x_data_coordinates=x_data_coordinates,
            y_data_structured=y_data_structured,
            **interpolated_model_kwargs,
        )

        self.x_data_raw_unstructured = x_data
        self.y_data_raw = y_data
示例#5
0
    def mesh_line(self,
                  x_nondim: Union[float, List[float]] = 0.25,
                  y_nondim: Union[float, List[float]] = 0,
                  add_camber: bool = True,
                  spanwise_resolution: int = 1,
                  spanwise_spacing: str = "cosine") -> np.ndarray:
        """
        Meshes a line that goes through each of the WingXSec objects in this wing.

        Args:

            x_nondim: The nondimensional (chord-normalized) x-coordinate that the line should go through. Can either
            be a single value used at all cross sections, or can be an iterable of values to be used at the
            respective cross sections.

            y_nondim: The nondimensional (chord-normalized) y-coordinate that the line should go through. Here,
            y-coordinate means the "vertical" component (think standard 2D airfoil axes). Can either be a single
            value used at all cross sections, or can be an iterable of values to be used at the respective cross
            sections.

            add_camber: Controls whether camber should be added to the line or not.

            spanwise_resolution: Controls the number of times each WingXSec is subdivided.

            spanwise_spacing: Controls the spanwise spacing. Either "cosine" or "uniform".

        Returns:

            points: a Nx3 np.ndarray that gives the coordinates of each point on the meshed line. Goes from the root
            to the tip. Ignores any wing symmetry (e.g., only gives one side).

        """

        if spanwise_spacing == "cosine":
            space = np.cosspace
        elif spanwise_spacing == "uniform":
            space = np.linspace
        else:
            raise ValueError("Bad value of 'spanwise_spacing'")

        xsec_points = []

        try:
            if len(x_nondim) != len(self.xsecs):
                raise ValueError(
                    "If x_nondim is going to be an iterable, it needs to be the same length as Airplane.xsecs."
                )
        except TypeError:
            pass

        try:
            if len(y_nondim) != len(self.xsecs):
                raise ValueError(
                    "If y_nondim is going to be an iterable, it needs to be the same length as Airplane.xsecs."
                )
        except TypeError:
            pass

        for i, xsec in enumerate(self.xsecs):

            try:
                xsec_x_nondim = x_nondim[i]
            except (TypeError, IndexError):
                xsec_x_nondim = x_nondim

            try:
                xsec_y_nondim = y_nondim[i]
            except (TypeError, IndexError):
                xsec_y_nondim = y_nondim

            if add_camber:
                xsec_y_nondim = xsec_y_nondim + xsec.airfoil.local_camber(
                    x_over_c=x_nondim)

            xsec_point = self._compute_xyz_of_WingXSec(
                i,
                x_nondim=xsec_x_nondim,
                y_nondim=xsec_y_nondim,
            )
            xsec_points.append(xsec_point)

        points_sections = []
        for i in range(len(xsec_points) - 1):
            points_section = np.stack([
                space(xsec_points[i][dim], xsec_points[i + 1][dim],
                      spanwise_resolution + 1) for dim in range(3)
            ],
                                      axis=1)
            if not i == len(xsec_points) - 2:
                points_section = points_section[:-1, :]

            points_sections.append(points_section)

        points = np.concatenate(points_sections)

        return points
示例#6
0
    right = [0, 1, 0]

    Uf, Vf, Wf = calculate_induced_velocity_horseshoe(
        x_field=Xf,
        y_field=Yf,
        z_field=Zf,
        x_left=left[0],
        y_left=left[1],
        z_left=left[2],
        x_right=right[0],
        y_right=right[1],
        z_right=right[2],
        gamma=1,
    )

    pos = np.stack((Xf, Yf, Zf)).T
    dir = np.stack((Uf, Vf, Wf)).T

    dir_norm = np.reshape(np.linalg.norm(dir, axis=1), (-1, 1))

    dir = dir / dir_norm * dir_norm ** 0.2

    import pyvista as pv

    pv.set_plot_theme('dark')
    plotter = pv.Plotter()
    plotter.add_arrows(
        cent=pos,
        direction=dir,
        mag=0.15
    )