Beispiel #1
0
 def _calc_area(self):
     # Calculates the panel area from the two constituent triangles
     A = 0.5 * norm(
         cross(self.vertices[1] - self.vertices[0],
               self.vertices[2] - self.vertices[0]))
     return A + 0.5 * norm(
         cross(self.vertices[2] - self.vertices[0],
               self.vertices[3] - self.vertices[0]))
Beispiel #2
0
    def calc_local_coords(self, **kwargs):
        """Calculates panel local coords (dependent on flow properties).

        Parameters
        ----------
        M : float
            Freestream Mach number.
        """

        # Get kwargs
        M = kwargs["M"]
        c_0 = kwargs["c_0"]
        C_0 = kwargs["C_0"]
        B_0 = kwargs["B_0"]
        s = kwargs["s"]
        B = kwargs["B"]

        # Calculate tangent vector compressible norms (if applicable)
        if hasattr(self, "t"):
            self.t_comp_norm = np.zeros(3)
            for i, t in enumerate(self.t):
                self.t_comp_norm[i] = inner(t, t)-M**2*inner(c_0, t)**2

        # Calculate conormal vector
        self.n_co = self.n-M**2*inner(c_0, self.n)*c_0

        # Check inclination
        self.n_co = np.einsum('ij,j', B_0, self.n)
        self._incl = inner(self.n, self.n_co)
        if abs(self._incl)<1e-10:
            raise MachInclinedError
        self._r = np.sign(self._incl)

        # Get panel coordinate directions
        v_0 = cross(self.n, c_0)
        v_0 /= norm(v_0)
        u_0 = cross(v_0, self.n)
        u_0 /= norm(u_0)

        # Calculate transformation matrix
        # It should be that det(A) = B**2 (see Epton & Magnus pp. E.3-16)
        self._A = np.zeros((3,3))
        denom = abs(self._incl)**-0.5
        self._A[0,:] = denom*np.einsum('ij,j', C_0, u_0)
        self._A[1,:] = self._r*s/B*np.einsum('ij,j', C_0, v_0)
        self._A[2,:] = B*denom*self.n

        # Calculate area Jacobian
        self._J = 1.0/B*denom
Beispiel #3
0
    def __init__(self, **kwargs):

        # Store vertices
        self.vertices = np.zeros((3,3))
        self.vertices[0] = kwargs["v0"]
        self.vertices[1] = kwargs["v1"]
        self.vertices[2] = kwargs["v2"]
        self._projected = kwargs.get("projected", False)

        # Calculate area and normal vector
        n = cross(self.vertices[1]-self.vertices[0], self.vertices[2]-self.vertices[1])
        N = norm(n)
        self.A = 0.5*N
        self.n = n/N

        # Check for zero area in projected panel
        if self.A<1e-10 and self._projected:
            self.null_panel = True
        else:
            self.null_panel = False

            # Calculate edge tangents
            self.t = np.roll(self.vertices, 1, axis=0)-self.vertices
            self.t /= np.linalg.norm(self.t, axis=1, keepdims=True)

            self._calc_geom_props()
Beispiel #4
0
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # Store type
        self._type = kwargs.get("fixed_direction_type",
                                "freestream_and_rotation")

        # Initialize filaments
        self._vertices, self.inbound_panels, self.outbound_panels = self._arrange_kutta_vertices(
        )

        # Store number of filaments and segments
        self.N = len(self._vertices)
        self.N_segments = 1

        # Initialize filament directions
        self.filament_dirs = np.zeros((self.N, 3))

        # Get direction for custom wake
        if self._type == "custom":
            try:
                u = np.array(kwargs.get("custom_dir"))
                u /= norm(u)
                self.filament_dirs[:] = u
            except:
                raise IOError(
                    "'custom_dir' is required for non-iterative wake type 'custom'."
                )
Beispiel #5
0
 def _calc_normal(self):
     # Calculates the panel unit normal vector
     # Assumes the panel is planar
     d1 = self.vertices[1] - self.vertices[0]
     d2 = self.vertices[2] - self.vertices[1]
     N = cross(d1, d2)
     return N / norm(N)
Beispiel #6
0
    def __init__(self, **kwargs):

        # Store vertices
        self.vertices = np.zeros((4,3))
        self.vertices[0] = kwargs.get("v0")
        self.vertices[1] = kwargs.get("v1")
        self.vertices[2] = kwargs.get("v2")
        self.vertices[3] = kwargs.get("v3", self.vertices[2]) # Will get removed by _check_collapsed_vertices()

        # Store edge number
        self.edge = kwargs.get("edge", [0])

        # Determine if this is a projected panel
        self._projected = kwargs.get("projected", False)

        # Check for collapsed points
        self._check_collapsed_vertices(kwargs.get("tol", 1e-8))

        # Calculate midpoints
        self.midpoints = 0.5*(self.vertices+np.roll(self.vertices, 1, axis=0))

        # Calculate normal vector; this is simpler than the method used in PAN AIR, which is able to handle
        # the case where the midpoints and center point do not lie in a flat plane [Epton & Magnus section D.2]
        self.n = cross(self.midpoints[1]-self.midpoints[0], self.midpoints[2]-self.midpoints[1])
        self.n /= norm(self.n)

        # Other calculations
        self._calc_geom_props()
        self._calc_skewness()

        # Setup projected panel
        if not self._projected:
            self._initialize_projected_panel()

        # Initialize subpanels
        self.subpanels = []
        for i in range(self.N):

            # Outer subpanel
            self.subpanels.append(Subpanel(v0=self.midpoints[i-1], v1=self.vertices[i], v2=self.midpoints[i], projected=self._projected))

            # Inner subpanel
            self.subpanels.append(Subpanel(v0=self.midpoints[i], v1=self.center, v2=self.midpoints[i-1], projected=self._projected))

        # Initialize half panels (only if the panel is not already triangular)
        if self.N==4:
            self.half_panels = []
            for i in range(self.N):
                self.half_panels.append(Subpanel(v0=self.vertices[i-2], v1=self.vertices[i-1], v2=self.vertices[i]))
        else:
            self.half_panels = False
Beispiel #7
0
    def _load_stl(self, stl_file):
        # Loads mesh from an stl file

        # Load stl file
        raw_mesh = stl.mesh.Mesh.from_file(stl_file)

        # Initialize storage
        N = raw_mesh.v0.shape[0]
        self.N = N
        self.panels = []
        bad_facets = []

        # Loop through panels and initialize objects
        for i in range(N):

            # Check for finite area
            if norm(raw_mesh.normals[i]) == 0.0:
                self.N -= 1
                warnings.warn("Panel {0} has zero area. Skipping...".format(i))
                bad_facets.append(i)
                continue

            # Initialize
            panel = Tri(v0=raw_mesh.v0[i],
                        v1=raw_mesh.v1[i],
                        v2=raw_mesh.v2[i])

            self.panels.append(panel)

        self.panels = np.array(self.panels)

        # Store panel information
        self.cp = np.zeros((self.N, 3))
        self.n = np.zeros((self.N, 3))
        self.dA = np.zeros(self.N)
        for i in range(self.N):
            self.n[i], self.dA[i], self.cp[i] = self.panels[i].get_info()

        # Get vertex list
        good_facets = [i for i in range(N) if i not in bad_facets]
        raw_vertices = np.concatenate(
            (raw_mesh.v0[good_facets], raw_mesh.v1[good_facets],
             raw_mesh.v2[good_facets]))
        self._vertices, inverse_indices = np.unique(raw_vertices,
                                                    return_inverse=True,
                                                    axis=0)
        self._panel_vertex_indices = []
        for i in range(self.N):
            self._panel_vertex_indices.append([3, *inverse_indices[i::self.N]])
Beispiel #8
0
    def update(self, velocity_from_body, mu, v_inf, omega, verbose):
        """Updates the shape of the wake based on solved flow results.

        Parameters
        ----------
        velocity_from_body : callable
            Function which will return the velocity induced by the body at a given set of points.

        mu : ndarray
            Vector of doublet strengths.

        v_inf : ndarray
            Freestream velocity vector.

        omega : ndarray
            Angular rate vector.

        verbose : bool
        """

        if verbose:
            print()
            prog = OneLineProgress(4, msg="    Updating wake shape")

        # Reorder vertices for computation
        points = self._vertices[:, 1:, :].reshape(
            (self.N * (self.N_segments), 3))

        # Get velocity from body and rotation
        v_ind = velocity_from_body(points) - vec_cross(omega, points)
        if verbose: prog.display()

        # Get velocity from wake elements
        v_ind += self._get_velocity_from_filaments_and_edges(points, mu)
        if verbose: prog.display()

        # Calculate time-stepping parameter
        U = norm(v_inf)
        u = v_inf / U
        dl = self._vertices[:, 1:, :] - self._vertices[:, 0, :][:,
                                                                np.newaxis, :]
        d = vec_inner(dl, u[np.newaxis, :])
        dt = self._K * d / U
        if verbose: prog.display()

        # Shift vertices
        self._vertices[:, 1:, :] += dt[:, :, np.newaxis] * v_ind.reshape(
            (self.N, self.N_segments, 3))
        if verbose: prog.display()
Beispiel #9
0
    def __init__(self, **kwargs):

        # Store vertices
        self.vertices = np.zeros((3, 3))
        self.vertices[0] = kwargs.get("v0")
        self.vertices[1] = kwargs.get("v1")
        self.vertices[2] = kwargs.get("v2")

        self.N = 3

        super().__init__(**kwargs)

        # Set up local coordinate transformation
        n, _, _ = self.get_info()
        self.A_t = np.zeros((3, 3))
        self.A_t[0] = self.vertices[1] - self.vertices[0]
        self.A_t[0] /= norm(self.A_t[0])
        self.A_t[1] = cross(n, self.A_t[0])
        self.A_t[2] = n
Beispiel #10
0
    def set_filament_direction(self, v_inf, omega):
        """Updates the direction of the vortex filaments based on the velocity params.

        Parameters
        ----------
        v_inf : ndarray
            Freestream velocity vector.

        omega : ndarray
            Angular rate vector.
        """

        # Freestream direction
        if self._type == "freestream":
            u = v_inf / norm(v_inf)
            self.filament_dirs[:] = u

        # Freestream with rotation
        elif self._type == "freestream_and_rotation":
            self.filament_dirs = v_inf[np.newaxis, :] - vec_cross(
                omega, self._vertices)
            self.filament_dirs /= vec_norm(self.filament_dirs)[:, np.newaxis]
Beispiel #11
0
    def __init__(self, **kwargs):

        # Store vertices
        self.vertices = np.zeros((4, 3))
        self.vertices[0] = kwargs.get("v0")
        self.vertices[1] = kwargs.get("v1")
        self.vertices[2] = kwargs.get("v2")
        self.vertices[3] = kwargs.get("v3")
        self.midpoints = 0.5 * (self.vertices +
                                np.roll(self.vertices, 1, axis=0))

        self.N = 4

        super().__init__(**kwargs)

        # Set up local coordinate transformation
        n = self._calc_normal()
        self.A_t = np.zeros((3, 3))
        self.A_t[0] = self.midpoints[1] - self.midpoints[0]
        self.A_t[0] /= norm(self.A_t[0])
        self.A_t[1] = cross(n, self.A_t[0])
        self.A_t[2] = n
Beispiel #12
0
    def get_vtk_data(self, **kwargs):
        """Returns a list of vertices and line indices describing this wake.
        
        Parameters
        ----------
        length : float, optional
            Length of the final filament segment, if set as infinite. Defaults to 20 times the filament segment length.
        """

        # Get kwargs
        l = kwargs.get("length", 20.0 * self.l)

        # Initialize storage
        vertices = []
        line_vertex_indices = []

        # Loop through filaments
        i = 0
        for j in range(self.N):

            # Add vertices
            for k in range(self.N_segments + 1):
                vertices.append(self._vertices[j, k])

                # Add indices
                if k != self.N_segments:
                    line_vertex_indices.append([2, i + k, i + k + 1])

            # Treat infinite end segment
            if self._end_infinite:
                u = vertices[-1] - vertices[-2]
                u /= norm(u)
                vertices[-1] = vertices[-2] + u * l

            # Increment index
            i += self.N_segments + 1

        return vertices, line_vertex_indices, self.N * self.N_segments
Beispiel #13
0
    def set_condition(self, **kwargs):
        """Sets the atmospheric conditions for the computation.

        V_inf : list
            Freestream velocity vector.

        rho : float
            Freestream density.
        
        angular_rate : list, optional
            Body-fixed angular rate vector (given in rad/s). Defaults to [0.0, 0.0, 0.0].
        """

        # Set solved flag
        self._solved = False

        # Get freestream
        self._v_inf = np.array(kwargs["V_inf"])
        self._V_inf = norm(self._v_inf)
        self._u_inf = self._v_inf / self._V_inf
        self._rho = kwargs["rho"]
        self._omega = np.array(kwargs.get("angular_rate", [0.0, 0.0, 0.0]))

        # Create part of b vector dependent upon v_inf and rotation
        v_rot = vec_cross(self._omega, self._mesh.cp)
        self._v_inf_and_rot = self._v_inf - v_rot
        self._b = -vec_inner(self._v_inf - v_rot, self._mesh.n)

        # Get solid body rotation
        self._omega = np.array(kwargs.get("angular_rate", [0.0, 0.0, 0.0]))

        # Finish Kutta edge search on mesh
        self._mesh.finalize_kutta_edge_search(self._u_inf)

        # Update wake
        self._mesh.wake.set_filament_direction(self._v_inf, self._omega)
Beispiel #14
0
    def finalize_kutta_edge_search(self, u_inf):
        """Determines where the Kutta condition should exist based on previously located adjacent panels and the freestream velocity.

        Parameters
        ----------
        u_inf : ndarray
            Freestream velocity vector (direction of the oncoming flow).
        """

        # Initialize edge storage
        self._kutta_edges = []

        if len(self._potential_kutta_panels) > 0:

            if self._verbose:
                print()
                prog = OneLineProgress(len(self._potential_kutta_panels),
                                       msg="Finalizing Kutta edge locations")

            # Loop through previously determined possibilities
            for i, j in self._potential_kutta_panels:

                # Get panel objects
                panel_i = self.panels[i]
                panel_j = self.panels[j]

                # Find first vertex shared by panels
                found = False
                for ii, vi in enumerate(panel_i.vertices):
                    for jj, vj in enumerate(panel_j.vertices):

                        # Check distance
                        if norm(vi - vj) < 1e-10:

                            # Store first shared vertex
                            v0 = vi
                            ii0 = ii
                            jj0 = jj
                            found = True
                            break

                    if found:
                        break

                # Find second shared vertex; will be adjacent to first
                poss_i_vert = [ii0 - 1, ii0 + 1]
                poss_j_vert = [(jj0 + 1) % panel_j.N, jj0 - 1]
                for i_same_dir, (ii, jj) in enumerate(
                        zip(poss_i_vert, poss_j_vert)
                ):  # i_same_dir keeps track of if ii is still increasing
                    vi = panel_i.vertices[ii]
                    vj = panel_j.vertices[jj]

                    # Check distance
                    if norm(vi - vj) < 1e-10:

                        # See if we need to check the freestream condition
                        is_kutta_edge = False
                        if not self._check_freestream:
                            is_kutta_edge = True

                        # Get edge normals
                        else:
                            edge_normals_i = panel_i.get_edge_normals()
                            edge_normals_j = panel_j.get_edge_normals()

                            # Decide which edge to use
                            if i_same_dir:
                                i_edge = ii0
                                j_edge = jj
                            else:
                                i_edge = ii
                                j_edge = jj0

                            # Check angle edge normals make with freestream
                            if inner(edge_normals_i[i_edge],
                                     u_inf) > 0.0 or inner(
                                         edge_normals_j[j_edge], u_inf) > 0.0:
                                is_kutta_edge = True

                        # Store
                        if is_kutta_edge:
                            if ii - ii0 == 1:  # Order is important for definition of circulation
                                self._kutta_edges.append(
                                    KuttaEdge(v0, vi, [i, j]))
                            else:
                                self._kutta_edges.append(
                                    KuttaEdge(vi, v0, [i, j]))
                            break

                if self._verbose:
                    prog.display()

            # Store number of edges
            self.N_edges = len(self._kutta_edges)

        else:
            self.N_edges = 0

        if self._verbose:
            print("    Found {0} Kutta edges.".format(self.N_edges))

        if self._verbose:
            print()
            prog = OneLineProgress(
                self.N, msg="Locating panels for gradient calculation")

        # Store touching and abutting panels not across Kutta edge
        for i, panel in enumerate(self.panels):

            # Loop through panels touching this one
            for j in panel.touching_panels:

                # Check for kutta edge
                for kutta_edge in self._kutta_edges:
                    pi = kutta_edge.panel_indices
                    if (pi[0] == i and pi[1] == j) or (pi[0] == j
                                                       and pi[1] == i):
                        break

                else:
                    panel.touching_panels_not_across_kutta_edge.append(j)

                    # Check if the panel is abutting
                    if j in panel.abutting_panels:
                        panel.abutting_panels_not_across_kutta_edge.append(j)

            if self._verbose:
                prog.display()

        # Store second abutting panels not across Kutta edge
        # Note we're not tracking the progress of this loop. It's super fast.
        for i, panel in enumerate(self.panels):
            for j in panel.abutting_panels_not_across_kutta_edge:

                # This panel obviously counts
                panel.second_abutting_panels_not_across_kutta_edge.append(j)

                # Get second panels
                for k in self.panels[j].abutting_panels_not_across_kutta_edge:
                    if k not in panel.second_abutting_panels_not_across_kutta_edge and k != i:
                        panel.second_abutting_panels_not_across_kutta_edge.append(
                            k)

        # Set up least-squares matrices
        self._set_up_lst_sq()

        # Initialize wake
        if self.N_edges > 0:
            if self._wake_type == "fixed":
                self.wake = StraightFixedWake(kutta_edges=self._kutta_edges,
                                              **self._wake_kwargs)
            elif self._wake_type == "full_streamline":
                self.wake = FullStreamlineWake(kutta_edges=self._kutta_edges,
                                               **self._wake_kwargs)
            elif self._wake_type == "relaxed":
                self.wake = VelocityRelaxedWake(kutta_edges=self._kutta_edges,
                                                **self._wake_kwargs)
            elif self._wake_type == "marching_streamline":
                self.wake = MarchingStreamlineWake(
                    kutta_edges=self._kutta_edges, **self._wake_kwargs)
            else:
                raise IOError(
                    "{0} is not a valid wake type.".format(wake_type))
Beispiel #15
0
 def _calc_area(self):
     # Calculates the panel area
     return 0.5 * norm(
         cross(self.vertices[1] - self.vertices[0],
               self.vertices[2] - self.vertices[0]))
Beispiel #16
0
 def _calc_normal(self):
     # Calculates the normal based off of the edge midpoints
     n = cross(self.midpoints[1] - self.midpoints[0],
               self.midpoints[2] - self.midpoints[1])
     return n / norm(n)