diff --git a/blast/interfaces/su2/blSU2Interface.py b/blast/interfaces/su2/blSU2Interface.py index 816ae48f7a660026dcdd0546ba9058f9e6ee47fa..0d3a5e461fd6158c32f5c66e9d2beba8ccc19ddc 100644 --- a/blast/interfaces/su2/blSU2Interface.py +++ b/blast/interfaces/su2/blSU2Interface.py @@ -168,14 +168,18 @@ class SU2Interface(SolversInterface): """ Extract the inviscid paramaters at the edge of the boundary layer and use it as a boundary condition in the viscous solver. """ - if self.getnDim() == 2: + region = self.iBnd[0][0] + wingRows = region.getNodeRows() + nNodes, ndim = region.getnNodes(), self.getnDim() + + if ndim == 2: velComponents = ['VELOCITY_X', 'VELOCITY_Y'] - elif self.getnDim() == 3: + elif ndim == 3: velComponents = ['VELOCITY_X', 'VELOCITY_Y', 'VELOCITY_Z'] else: raise RuntimeError('su2Interface::Incorrect number of dimensions.') - velIdx = np.zeros(self.getnDim(), dtype=int) + velIdx = np.zeros(ndim, dtype=int) for i, velComp in enumerate(velComponents): velIdx[i] = self.solver.GetPrimitiveIndices()[velComp] soundSpeedIdx = self.solver.GetPrimitiveIndices()["SOUND_SPEED"] @@ -183,18 +187,117 @@ class SU2Interface(SolversInterface): for body in self.iBnd: for reg in body: - for inode, nodeIndexFloat in enumerate(reg.nodesCoord[:,-1]): + V = np.empty((nNodes, ndim)) + M = np.empty(nNodes) + Rho = np.empty(nNodes) + for inode, nodeIndexFloat in enumerate(wingRows): nodeIndex = int(nodeIndexFloat) # Velocity - for idim in range(self.getnDim()): - reg.V[inode,idim] = self.solver.Primitives().Get(int(nodeIndex), int(velIdx[idim])) + for idim in range(ndim): + V[inode,idim] = self.solver.Primitives().Get(int(nodeIndex), int(velIdx[idim])) # Density - reg.Rho[inode] = self.solver.Primitives().Get(nodeIndex, densityIdx) + Rho[inode] = self.solver.Primitives().Get(nodeIndex, densityIdx) # Mach number vel_norm = 0.0 - for idim in range(self.getnDim()): - vel_norm += reg.V[inode,idim]**2 - reg.M[inode] = np.sqrt(vel_norm) / self.solver.Primitives().Get(nodeIndex, soundSpeedIdx) + for idim in range(ndim): + vel_norm += V[inode,idim]**2 + M[inode] = np.sqrt(vel_norm) / self.solver.Primitives().Get(nodeIndex, soundSpeedIdx) + reg.updateVariables(M, V, Rho) + + from matplotlib import pyplot as plt + plt.plot(reg._nodesCoord[:,0], reg._M, 'x-') + + # Reflect the last two nodes + nodes_ = region.getNodes().copy() + M_ = M.copy() + new_nodes, new_M = self.orthogonal_reflection(nodes_, M_) + #plt.plot(new_nodes[:,0], new_M, 'o-') + #plt.show() + smoothed_nodes, smoothed_M = self.explicit_smoother(new_nodes, new_M, iterations=10) + plt.plot(smoothed_nodes[:,0], smoothed_M, 'o-') + + plt.show() + quit() + plt.show() + + def orthogonal_reflection(self, nodes, M, axis_x=1): + """ + Reflect the first two and last two points of the Mach number data (simulating wake) with respect + to the given axis (default trailing edge at x=1), and append them to the data. + + Args: + - nodes: 2D array where each row contains [x, y, z] coordinates. + - M: 1D array containing the Mach numbers. + - axis_x: The x-coordinate about which to reflect (default is 1, the trailing edge). + + Returns: + - new_nodes: Reflected nodes including the additional wake simulation points. + - new_M: Reflected Mach numbers. + """ + # Reflect the first two nodes and Mach numbers + first_two_nodes = nodes[1:5] # First two [x, y, z] + first_two_M = M[1:5] # First two Mach numbers + + # Orthogonal reflection: Reflect the x-values with respect to x=axis_x + reflected_first_two_nodes = np.copy(first_two_nodes) + reflected_first_two_nodes[:, 0] = 2*axis_x - first_two_nodes[:, 0] # Reflect x-values + + # Reflect the Mach numbers + reflected_first_two_M = first_two_M[::-1] # Reverse the order of the first two Mach numbers + + # Reflect the last two nodes and Mach numbers + last_two_nodes = nodes[-5:-1] # Last two [x, y, z] + last_two_M = M[-5:-1] # Last two Mach numbers + + # Orthogonal reflection: Reflect the x-values with respect to x=axis_x + reflected_last_two_nodes = np.copy(last_two_nodes) + reflected_last_two_nodes[:, 0] = 2*axis_x - last_two_nodes[:, 0] # Reflect x-values + + # Reflect the Mach numbers + reflected_last_two_M = last_two_M[::-1] # Reverse the order of the last two Mach numbers + + # Append the reflected points to the original nodes and Mach numbers + new_nodes = np.vstack((reflected_first_two_nodes, nodes, reflected_last_two_nodes)) + new_M = np.concatenate((reflected_first_two_M, M, reflected_last_two_M)) + + return new_nodes, new_M + + def explicit_smoother(self, nodes, M, mask_range=(0.8, 1.0), iterations=5): + """ + Smooth the Mach number data only for nodes in a given range (between mask_range[0] and mask_range[1]). + The smoothing uses explicit finite difference method with Neumann boundary conditions. + + Args: + - nodes: 2D array where each row contains [x, y, z] coordinates. + - M: 1D array containing the Mach numbers. + - mask_range: Tuple (start_pct, end_pct) defining the range of x-values to apply smoothing. + - iterations: Number of smoothing iterations to perform. + + Returns: + - smoothed_nodes: Original nodes (unchanged). + - smoothed_M: Smoothed Mach number data. + """ + # Create a mask for the region between 0.8 and 1.0 of the chord + mask = (nodes[:, 0] > mask_range[0]) & (nodes[:, 0] < mask_range[1]) + + smoothed_M = M.copy() # Copy the Mach numbers for smoothing + + for _ in range(iterations): + new_M = smoothed_M.copy() + + # Apply smoothing only to the values in the specified range (using the mask) + for i in range(1, len(M) - 1): + if mask[i]: # Only smooth where the mask is True + new_M[i] = 0.5 * (smoothed_M[i - 1] + smoothed_M[i + 1]) + + te_index = np.argmax(nodes[:, 0]) # Index of the trailing edge + # Neumann boundary condition: no change at the first and last points + new_M[0] = smoothed_M[1] # Set first point equal to the second point + new_M[-1] = smoothed_M[-2] # Set last point equal to the second-to-last point + + smoothed_M = new_M + + return nodes, smoothed_M def setBlowingVelocity(self): """ @@ -211,41 +314,47 @@ class SU2Interface(SolversInterface): be projected into the global frame and interpolated to the nodes. """ # Compute blowing velocity in the global frame of reference on the elements - blw_elems = np.zeros((self.iBnd[0][0].elemsCoord.shape[0], self.getnDim()), dtype=float) + region = self.iBnd[0][0] + nNodes, nElms, ndim = region.getnNodes(), region.getnElms(), self.getnDim() + rowsWing = region.getNodeRows() + blowingWing = region.getBlowingVelocity() + nodesWing = region.getNodes() + + blw_elems_normal = np.zeros((nElms, ndim), dtype=float) normals = [] - for ielm in range(len(self.iBnd[0][0].elemsCoord)): + for ielm in range(len(self.iBnd[0][0].getElms('all'))): nod1 = ielm nod2 = ielm + 1 - x1 = self.iBnd[0][0].nodesCoord[nod1,:self.getnDim()] - x2 = self.iBnd[0][0].nodesCoord[nod2,:self.getnDim()] + x1 = nodesWing[nod1,:ndim] + x2 = nodesWing[nod2,:ndim] # Components of the tangent vector - t = np.empty(self.getnDim()) - for idim in range(self.getnDim()): + t = np.empty(ndim) + for idim in range(ndim): t[idim] = x2[idim] - x1[idim] normt = np.linalg.norm(t) # Components of the normal vector - n = np.empty(self.getnDim()) - if self.getnDim() == 2: + n = np.empty(ndim) + if ndim == 2: # 90° rotation n[0] = t[1] / normt n[1] = -t[0] / normt - elif self.getnDim() == 3: + elif ndim == 3: # Compute using cross product with another edge raise RuntimeError('3D normal not implemented yet (use SU2 function if possible).') normals.append(n) - for idim in range(self.getnDim()): - blw_elems[ielm, idim] = self.iBnd[0][0].blowingVel[ielm] * n[idim] + for idim in range(ndim): + blw_elems_normal[ielm, idim] = blowingWing[ielm] * n[idim] # Compute blowing velocity at nodes - blw_nodes = np.zeros((self.iBnd[0][0].nodesCoord.shape[0], 3), dtype=float) - for inode in range(self.iBnd[0][0].nodesCoord.shape[0]): + blw_nodes = np.zeros((nNodes, 3), dtype=float) + for inode in range(nNodes): # Look for the elements sharing node inode # At TE, elm1 is the last element of upper side and elm2 is the last element of lower side. - if inode == 0 or inode == self.iBnd[0][0].nodesCoord.shape[0]-1: + if inode == 0 or inode == nNodes-1: elm1 = 0 elm2 = -1 else: @@ -254,23 +363,26 @@ class SU2Interface(SolversInterface): # Because the blowing velocity on the lower side points downwards sign = 1 - if inode > self.iBnd[0][0].nodesCoord.shape[0]//2: + if inode > nNodes//2: sign = -1 # The blowing velocity on one node is the average # of the blowing velocity on the elements sharing the node - for idim in range(self.getnDim()): - blw_nodes[inode, idim] = sign * 0.5*(blw_elems[elm1, idim] + blw_elems[elm2, idim]) + for idim in range(ndim): + blw_nodes[inode, idim] = sign * 0.5*(blw_elems_normal[elm1, idim] + blw_elems_normal[elm2, idim]) - blw_nodes[blw_nodes < -0.01] = -0.01 - blw_nodes[blw_nodes > 0.01] = 0.01 + # blw_nodes[blw_nodes < -0.01] = -0.01 + # blw_nodes[blw_nodes > 0.01] = 0.01 # Set blowing velocity in SU2 solver for arfTag in self.wingTags: for ivertex in range(self.solver.GetNumberMarkerNodes(self.wingMarkersToID[arfTag])): nodeIndex = self.solver.GetMarkerNode(self.wingMarkersToID[arfTag], ivertex) - blasterIndex = np.where(self.iBnd[0][0].nodesCoord[:, -1] == nodeIndex)[0][0] - self.solver.SetBlowingVelocity(nodeIndex, blw_nodes[blasterIndex, 0], blw_nodes[blasterIndex, 1], blw_nodes[blasterIndex, 2]) + blasterIndex = np.where(rowsWing == nodeIndex)[0][0] + self.solver.SetBlowingVelocity(nodeIndex, + blw_nodes[blasterIndex, 0], + blw_nodes[blasterIndex, 1], + blw_nodes[blasterIndex, 2]) def getWing(self): if self.getnDim() == 2: @@ -340,11 +452,14 @@ class SU2Interface(SolversInterface): raise RuntimeError('su2Interface::Duplicated elements in airfoil mesh.') # Fill the data structures - self.iBnd[ibody][0].initStructures(nodesCoord.shape[0], elemsCoord.shape[0]) - self.iBnd[ibody][0].nodesCoord = nodesCoord - self.iBnd[ibody][0].elemsCoord = elemsCoord - self.iBnd[ibody][0].setConnectList(np.array(nodesCoord[:,-1],dtype=int), + self.iBnd[ibody][0].setMesh(nodesCoord[:,:3], elemsCoord[:,:3]) + #self.iBnd[ibody][0].initStructures(nodesCoord.shape[0], elemsCoord.shape[0]) + #self.iBnd[ibody][0].nodesCoord = nodesCoord + #self.iBnd[ibody][0].elemsCoord = elemsCoord + self.iBnd[ibody][0].setConnectLists(np.array(nodesCoord[:,-1],dtype=int), np.array(elemsCoord[:,-1],dtype=int)) + self.iBnd[ibody][0].setRows(np.array(nodesCoord[:,-1],dtype=int), + np.array(elemsCoord[:,-1],dtype=int)) def _modify_meshpath(self, config, mesh): # Open config file and look for line MESH_FILENAME diff --git a/blast/tests/su2/t_n0012_su2_2D.py b/blast/tests/su2/t_n0012_su2_2D.py index fc1d0514ff5dbbbefcbf3e14ee52bf59e29d461e..c38672dac27cdefe7468777804124bf1f41f31fc 100644 --- a/blast/tests/su2/t_n0012_su2_2D.py +++ b/blast/tests/su2/t_n0012_su2_2D.py @@ -50,13 +50,12 @@ def cfgBlast(): } def main(): - return 0 args = parseargs() icfg = cfgSu2(args.verb) vcfg = cfgBlast() coupler, isol, vsol = vutils.initBlast(icfg, vcfg, iSolver='SU2', task='analysis') - #coupler.run() - isol.run() + coupler.run() + #isol.run() print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET) tests = CTests()