def get_vortex_influence(self, points): """Determines the velocity vector induced by this edge at arbitrary points, assuming a horseshoe vortex is shed from this edge. Parameters ---------- points : ndarray An array of points where the first index is the point index and the second index is the coordinate. Returns ------- ndarray The velocity vector induced at each point. """ # Determine displacement vectors r0 = points - self.vertices[0, :] r1 = points - self.vertices[1, :] # Determine displacement vector magnitudes r0_mag = vec_norm(r0) r1_mag = vec_norm(r1) # Calculate influence of bound segment with np.errstate(divide='ignore'): d = (np.pi * r0_mag * r1_mag * (r0_mag * r1_mag + vec_inner(r0, r1))) n = 0.25 * ((r0_mag + r1_mag) / d) n = np.nan_to_num(n, copy=False) return n[:, np.newaxis] * vec_cross(r0, r1)
def get_ring_influence(self, points): """Determines the velocity vector induced by this panel at arbitrary points, assuming a vortex ring (0th order) model and a unit positive vortex strength. Parameters ---------- points : ndarray An array of points where the first index is the point index and the second index is the coordinate. Returns ------- ndarray The velocity vector induced at each point. """ # Determine displacement vectors r = points[np.newaxis, :, :] - self.vertices[:, np.newaxis, :] r_mag = vec_norm(r) # Calculate influence v = np.zeros_like(points) with np.errstate(divide='ignore'): for i in range(self.N): d = (r_mag[i - 1] * r_mag[i] * (r_mag[i - 1] * r_mag[i] + vec_inner(r[i - 1], r[i]))) n = (r_mag[i - 1] + r_mag[i]) / d n = np.nan_to_num(n, copy=False) v += vec_cross(r[i - 1], r[i]) * n[:, np.newaxis] return 0.25 / np.pi * v
def _get_filament_influences(self, points): # Determines the unit vortex influence from the wake filaments on the given points # Determine displacement vectors: first index is point, second is filament, third is segment, fourth is vector component if self._end_infinite: r0 = points[:, np.newaxis, np.newaxis, :] - self._vertices[ np.newaxis, :, : -2, :] # Don't add the last segment at this point r1 = points[:, np.newaxis, np.newaxis, :] - self._vertices[np.newaxis, :, 1:-1, :] else: r0 = points[:, np.newaxis, np.newaxis, :] - self._vertices[np.newaxis, :, :-1, :] r1 = points[:, np.newaxis, np.newaxis, :] - self._vertices[np.newaxis, :, 1:, :] # Determine displacement vector magnitudes r0_mag = vec_norm(r0) r1_mag = vec_norm(r1) # Calculate influence of each segment inf = np.sum( ((r0_mag + r1_mag) / (r0_mag * r1_mag * (r0_mag * r1_mag + vec_inner(r0, r1))))[:, :, :, np.newaxis] * vec_cross(r0, r1), axis=2) # Add influence of last segment, if needed if self._end_infinite: # Determine displacement vector magnitudes r = r1[:, :, -1, :] r_mag = vec_norm(r) u = self._vertices[:, -1, :] - self._vertices[:, -2, :] u /= vec_norm(u)[:, np.newaxis] # Calculate influence inf += vec_cross(u[np.newaxis, :, :], r) / ( r_mag * (r_mag - vec_inner(u[np.newaxis, :, :], r)))[:, :, np.newaxis] return 0.25 / np.pi * np.nan_to_num(inf)
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()
def _get_filament_influences(self, points): # Determines the unit vortex influence from the wake filaments on the given points # Determine displacement vectors: first index is point, second is filament, third is segment, fourth is vector component r0 = points[:, np.newaxis, np.newaxis, :] - self._vertices[ np.newaxis, :, :self.N_segments, :] r1 = points[:, np.newaxis, np.newaxis, :] - self._vertices[np.newaxis, :, 1:self.N_segments + 1, :] # Determine displacement vector magnitudes r0_mag = vec_norm(r0) r1_mag = vec_norm(r1) # Calculate influence of each segment inf = np.sum( ((r0_mag + r1_mag) / (r0_mag * r1_mag * (r0_mag * r1_mag + vec_inner(r0, r1))))[:, :, :, np.newaxis] * vec_cross(r0, r1), axis=2) return 0.25 / np.pi * np.nan_to_num(inf)
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)
def export_vtk(self, filename): """Exports the solver results on the mesh to a VTK file. If a wake exists, the only meaningful scalar result which will be specified on the wake filaments is the filament strength. All other results are arbitrarily set to zero on the wake. Parameters ---------- filename : str Name of the file to write the results to. Must have '.vtk' extension. """ # Check extension if '.vtk' not in filename: raise IOError( "Filename for VTK export must contain .vtk extension.") # Open file with open(filename, 'w') as export_handle: # Write header print("# vtk DataFile Version 3.0", file=export_handle) print( "PyPan results file. Generated by PyPan, USU AeroLab (c) 2020.", file=export_handle) print("ASCII", file=export_handle) # Write dataset print("DATASET POLYDATA", file=export_handle) # Write vertices vertices, panel_indices = self._mesh.get_vtk_data() wake_vertices, wake_filament_indices, N_segments = self._mesh.wake.get_vtk_data( ) print( "POINTS {0} float".format(len(vertices) + len(wake_vertices)), file=export_handle) for vertex in vertices: print("{0:<20.12}{1:<20.12}{2:<20.12}".format(*vertex), file=export_handle) for vertex in wake_vertices: print("{0:<20.12}{1:<20.12}{2:<20.12}".format(*vertex), file=export_handle) # Determine wake filament list size size = 0 for li in wake_filament_indices: size += len(li) # Write wake filaments print("LINES {0} {1}".format(N_segments, size), file=export_handle) for filament in wake_filament_indices: print(" ".join([ str(index) if i == 0 else str(index + len(vertices)) for i, index in enumerate(filament) ]), file=export_handle) # Determine polygon list size size = 0 for pi in panel_indices: size += len(pi) # Write panel polygons print("POLYGONS {0} {1}".format(self._N_panels, size), file=export_handle) for panel in panel_indices: print(" ".join([str(i) for i in panel]), file=export_handle) # Write flow results print("CELL_DATA {0}".format(self._N_panels + N_segments), file=export_handle) # Normals print("NORMALS panel_normals float", file=export_handle) for i in range(N_segments): print("0.00 0.00 0.00", file=export_handle) for n in self._mesh.n: print("{0:<20.12} {1:<20.12} {2:<20.12}".format( n[0], n[1], n[2]), file=export_handle) # Pressure coefficient print("SCALARS pressure_coefficient float 1", file=export_handle) print("LOOKUP_TABLE default", file=export_handle) for i in range(N_segments): print("0.0", file=export_handle) for C_P in self._C_P: print("{0:<20.12}".format(C_P), file=export_handle) # Singularity strength if hasattr(self, "_mu"): print("SCALARS doublet_strength float 1", file=export_handle) print("LOOKUP_TABLE default", file=export_handle) for i in range(self._mesh.wake.N): # Determine strength of filament mu = 0 # Add for outbound panels outbound_panels = self._mesh.wake.outbound_panels[i] if len(outbound_panels) > 0: mu -= self._mu[outbound_panels[0]] mu += self._mu[outbound_panels[1]] # Add for inbound panels inbound_panels = self._mesh.wake.inbound_panels[i] if len(inbound_panels) > 0: mu += self._mu[inbound_panels[0]] mu -= self._mu[inbound_panels[1]] # Print out for i in range(self._mesh.wake.N_segments): print("{0:<20.12}".format(mu), file=export_handle) for mu in self._mu: print("{0:<20.12}".format(mu), file=export_handle) # Velocity if hasattr(self, "_v"): print("VECTORS velocity float", file=export_handle) for i in range(N_segments): print("0.0 0.0 0.0", file=export_handle) for v in self._v: print("{0:<20.12} {1:<20.12} {2:<20.12}".format( v[0], v[1], v[2]), file=export_handle) # Normal velocity print("SCALARS normal_velocity float", file=export_handle) print("LOOKUP_TABLE default", file=export_handle) for i in range(N_segments): print("0.0", file=export_handle) for v_n in vec_inner(self._v, self._mesh.n): print("{0:<20.12}".format(v_n), file=export_handle) if self._verbose: print() print( "Case results successfully written to '{0}'.".format(filename))
def get_influence_matrix(self, **kwargs): """Create wake influence matrix; first index is the influenced panels, second is the influencing panel, third is the velocity component. Parameters ---------- points : ndarray Array of points at which to calculate the influence. N_panels : int Number of panels in the mesh to which this wake belongs. Returns ------- ndarray Trailing vortex influences. """ # Get kwargs points = kwargs.get("points") # Initialize storage N = len(points) vortex_influence_matrix = np.zeros((N, kwargs["N_panels"], 3)) # Get influence of edges for edge in self._kutta_edges: # Get indices of panels defining the edge p_ind = edge.panel_indices # Get infulence V = edge.get_vortex_influence(points) # Store vortex_influence_matrix[:, p_ind[0]] = -V vortex_influence_matrix[:, p_ind[1]] = V # Determine displacement vector magnitudes r = points[:, np.newaxis, :] - self._vertices[np.newaxis, :, :] r_mag = vec_norm(r) # Calculate influences V = 0.25 / np.pi * vec_cross( self.filament_dirs[np.newaxis, :, :], r) / (r_mag * (r_mag - vec_inner(self.filament_dirs[np.newaxis, :, :], r)) )[:, :, np.newaxis] for i in range(self.N): # Add for outbound panels outbound_panels = self.outbound_panels[i] if len(outbound_panels) > 0: vortex_influence_matrix[:, outbound_panels[0], :] -= V[:, i, :] vortex_influence_matrix[:, outbound_panels[1], :] += V[:, i, :] # Add for inbound panels inbound_panels = self.inbound_panels[i] if len(inbound_panels) > 0: vortex_influence_matrix[:, inbound_panels[0], :] += V[:, i, :] vortex_influence_matrix[:, inbound_panels[1], :] -= V[:, i, :] return vortex_influence_matrix