diff --git a/sdpm/CMakeLists.txt b/sdpm/CMakeLists.txt index 56669ca1dc8b70b0d2c0338aca3764ca21b154ff..cf2c99b668739d2660727448273ccd0a1d4447d6 100644 --- a/sdpm/CMakeLists.txt +++ b/sdpm/CMakeLists.txt @@ -23,4 +23,5 @@ MACRO_AddTest(${CMAKE_CURRENT_SOURCE_DIR}/tests) #FILE(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/utils.py") #INSTALL(FILES ${files} DESTINATION ${CMAKE_INSTALL_PREFIX}) INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/utils.py" DESTINATION ${CMAKE_INSTALL_PREFIX}) +INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/vtk_utils.py" DESTINATION ${CMAKE_INSTALL_PREFIX}) INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/gmsh.py" DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/sdpm/src/sdpmElement.h b/sdpm/src/sdpmElement.h index a42c4105e8a462664721386da86dcb4585495b3f..36c41dc79b9ebd7e2e18402f5b95f7bd694b8486 100644 --- a/sdpm/src/sdpmElement.h +++ b/sdpm/src/sdpmElement.h @@ -77,6 +77,7 @@ public: virtual void update(sdpmDouble beta); // Setters void setNode(size_t i, Node *n) { _nodes[i] = n; } +#endif // Getters size_t getId() const { return _id; } size_t const &getIdRef() const { return _id; } @@ -89,6 +90,7 @@ public: inline sdpmVector3d const &getCompressibleCg() const; inline sdpmDouble getCompressibleSurfaceArea() const; inline sdpmVector3d const &getCompressibleNormal() const; +#ifndef SWIG // Utilities virtual sdpmVector3d computeGradient(std::vector<sdpmDouble> const &u) const; virtual void write(std::ostream &out) const override; diff --git a/sdpm/src/sdpmSolver.h b/sdpm/src/sdpmSolver.h index a4120442c1c712f37c9fbc6de4482083aa4980ce..043735246428c9442a63440c461317ba227d8b9f 100644 --- a/sdpm/src/sdpmSolver.h +++ b/sdpm/src/sdpmSolver.h @@ -62,14 +62,16 @@ public: Solver(Problem &pbl); ~Solver(); + std::vector<sdpmDouble> const &getPressure() const { return _cp; } + std::vector<sdpmComplex> const &getUnsteadyPressure(size_t imd, size_t ifq) const { return _cp1[imd][ifq]; } sdpmDouble getLiftCoeff() const { return _cl; } sdpmDouble getDragCoeff() const { return _cd; } sdpmDouble getSideforceCoeff() const { return _cs; } sdpmDouble getMomentCoeff() const { return _cm; } - sdpmComplex getUnsteadyLiftCoeff(size_t m, size_t k) const { return _cl1[m][k]; } - sdpmComplex getUnsteadyDragCoeff(size_t m, size_t k) const { return _cd1[m][k]; } - sdpmComplex getUnsteadySideforceCoeff(size_t m, size_t k) const { return _cs1[m][k]; } - sdpmComplex getUnsteadyMomentCoeff(size_t m, size_t k) const { return _cm1[m][k]; } + sdpmComplex getUnsteadyLiftCoeff(size_t imd, size_t ifq) const { return _cl1[imd][ifq]; } + sdpmComplex getUnsteadyDragCoeff(size_t imd, size_t ifq) const { return _cd1[imd][ifq]; } + sdpmComplex getUnsteadySideforceCoeff(size_t imd, size_t ifq) const { return _cs1[imd][ifq]; } + sdpmComplex getUnsteadyMomentCoeff(size_t imd, size_t ifq) const { return _cm1[imd][ifq]; } void update(); void run(); @@ -82,7 +84,7 @@ public: private: void post(sdpmVectorXd const &etau, sdpmVectorXd const &emu); - void postUnsteady(size_t m, size_t k, sdpmVectorXcd const &etau, sdpmVectorXcd const &emu); + void postUnsteady(size_t imd, size_t ifq, sdpmVectorXcd const &etau, sdpmVectorXcd const &emu); }; } // namespace sdpm diff --git a/sdpm/utils.py b/sdpm/utils.py index a3320e02632b2f45e3d879ce37ed4663d7872b2e..547034a3ca8da214ba43982c5be19deb3fe0c3aa 100644 --- a/sdpm/utils.py +++ b/sdpm/utils.py @@ -43,3 +43,71 @@ def build_fpath(name, caller, recurse_lvl=0): for _ in range(recurse_lvl): recurse = os.path.join(recurse, '..') return os.path.abspath(os.path.join(os.path.dirname(caller), recurse, name)) + +def sort(conn, pts_vals): + """Sort value array against line connectivity list + + Parameters + ---------- + connct : 2d numpy array + connectivity list (n_cells X n_point_by_cell) + pts_vals : 2d numpy array + points coordinates and values + """ + import numpy as np + # sort id vector + conn = conn[conn[:,0].argsort(), :] + # deep copy of data + _copy = np.zeros((pts_vals.shape[0], pts_vals.shape[1])) + _copy[:] = pts_vals + # sort data against id + _max = np.argmax(pts_vals[:, 0]) + pts_vals[0,:] = _copy[_max, :] + nextId = conn[np.where(conn[:, 0]==_max)[0][0], 0] + for i in range(1, conn.shape[0]): + pts_vals[i,:] = _copy[conn[nextId,1], :] + nextId = conn[nextId, 1] + +def create_slices(body, sol, fname, ys): + """Create slices along the span and extract the chordwise pressure coefficient distribution + + Parameters + ---------- + body : sdpmBody + body data structure containing grid points and cells + sol : dict(string, array) + name and solution data at cells + fname : string + name of the .vtu file (without extension) + ys : array + y-coordinates defining cutplanes + """ + from sdpm.vtk_utils import write_grid, cut_grid, extract_data + import numpy as np + # Create grid and write to file + grid = write_grid(fname, body, sol) + # Extract data at along spanwise sections + snames = list(sol.keys()) + for i in range(len(ys)): + # get data on cut section + coords, connct, cvalues = extract_data(cut_grid(grid, [0., ys[i], 0.]), snames) + # normalize x-coordinate by local chord + xc = np.zeros((coords.shape[0], 1)) + xc[:,0] = (coords[:,0] - min(coords[:,0])) / (max(coords[:,0]) - min(coords[:,0])) + # average value at points from values at cell center + nvalues = np.zeros((coords.shape[0], len(sol.keys()))) + for j in range(connct.shape[0]): + pids = connct[j] + for k in range(len(pids)): + for l in range(len(sol.keys())): + nvalues[connct[j][k], l] += cvalues[snames[l]][j] / len(pids) + # create dataset holding x-coordinates and values and sort it + xc_vals = np.hstack((xc, nvalues)) + sort(connct, xc_vals) + xc_vals = np.vstack((xc_vals, xc_vals[0,:])) + # write to file + hdr = 'x/c' + for name in snames: + hdr += f', {name}' + hdr += f'\ny = {ys[i]}' + np.savetxt(fname+f'_slice_{i}.dat', xc_vals, fmt='%1.5e', delimiter=',', header=hdr) diff --git a/sdpm/vtk_utils.py b/sdpm/vtk_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6a65a8e02c95b25e53ae4d2ded425ed8643aa747 --- /dev/null +++ b/sdpm/vtk_utils.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- + +# Copyright 2023 University of Liège +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import vtk + +def read_grid(fname): + """Read VTU file from disk + + Parameters + ---------- + fname : string + name of the .vtu file (without extension) + + Return + ------ + grid : vtkUnstructuredGrid + grid in VTU format containing points, cells, and cells data + """ + import os.path + # Check + fname += '.vtu' + if not os.path.exists(fname): + raise RuntimeError('File does not exists!\n') + # Read + reader = vtk.vtkXMLUnstructuredGridReader() + reader.SetFileName(fname) + reader.Update() + return reader.GetOutputPort() + +def write_grid(fname, body, sol): + """Write VTU file from disk + + Parameters + ---------- + fname : string + name of the .vtu file (without extension) + body : sdpmBody + body data structure containing grid points and cells + sol : dict(string, array) + name and solution data at cells + + Return + ------ + grid : vtkUnstructuredGrid + grid in VTU format containing points, cells, and cells data + """ + # Create grid data structure + grid = vtk.vtkUnstructuredGrid() + points = vtk.vtkPoints() + cells = vtk.vtkCellArray() + ctypes = [] + + # Add points + node_point_map = {} + for n in body.getNodes(): + coords = n.getCoords() + node_point_map[n.getId()] = points.InsertNextPoint(coords[0], coords[1], coords[2]) + grid.SetPoints(points) + + # Add cells + elem_cell_map = {} + for e in body.getElements(): + # create cell and set number of points + nodes = e.getNodes() + cell = vtk.vtkQuad() + cell.GetPointIds().SetNumberOfIds(len(nodes)) + # add each vertex point + for i, n in enumerate(nodes): + cell.GetPointIds().SetId(i, node_point_map[n.getId()]) + # add cell to grid + elem_cell_map[e.getId()] = cells.InsertNextCell(cell) + ctypes.append(cell.GetCellType()) + grid.SetCells(ctypes, cells) + + # Add values at cells + for name, vals in sol.items(): + # fill the values + values = vtk.vtkDoubleArray() + for e in body.getElements(): + eid = e.getId() + values.InsertValue(elem_cell_map[eid], vals[eid - 1]) + # add values to the grid + values.SetName(name) + grid.GetCellData().AddArray(values) + + # Write to file + fname += '.vtu' + writer = vtk.vtkXMLUnstructuredGridWriter() + writer.SetFileName(fname) + writer.SetInputData(grid) + writer.Write() + return grid + +def cut_grid(grid, org, nrm=[0.,1.,0.]): + """Perform a cut on a grid using a plane defined by its origin and normal vector + + Parameters + ---------- + grid : vtkUnstructuredGrid + grid in VTU format containing points, cells, and cells data + org : array + origin of cut plane + nrm : array + normal vector to cut plane + + Return + ------ + vtkPolyData + dataset holding points, cells and data + """ + # Create cutplane + plane = vtk.vtkPlane() + plane.SetOrigin(org[0], org[1], org[2]) + plane.SetNormal(nrm[0], nrm[1], nrm[2]) + # Create cutting tool + cutter = vtk.vtkCutter() + cutter.SetCutFunction(plane) + cutter.SetInputData(grid) + # Perform cut + cutter.Update() + return cutter.GetOutput() + +def extract_data(data, vname): + """Extract point coordinates, connectivity list and values from a dataset + + Parameters + ---------- + data : vtkPolyData + dataset holding points, cells and data + + Return + ------ + coords : 2d numpy array + array of coordinates (n_points X 3) + connct : 2d numpy array + connectivity list (n_cells X n_point_by_cell) + values : dict(name, 2d numpy array) + name and data at cut cells + """ + import numpy as np + # Transfer point coordinates + _pts = data.GetPoints() + pts = np.zeros((_pts.GetNumberOfPoints(), 3)) + for i in range(pts.shape[0]): + for j in range(3): + pts[i][j] = _pts.GetPoint(i)[j] + # Transfer connectivity + _elems = data.GetLines().GetData() + nvert = 2 + elems = np.zeros((_elems.GetNumberOfTuples() // (nvert+1), nvert), dtype=int) + for i in range(elems.shape[0]): + for j in range(nvert): + elems[i][j] = _elems.GetTuple((nvert+1)*i+j+1)[0] + # Transfer variables + vals = {} + for name in vname: + _vals = data.GetCellData().GetArray(name) + vals[name] = np.zeros((_vals.GetNumberOfTuples(), _vals.GetNumberOfComponents())) + for i in range(vals[name].shape[0]): + for j in range(vals[name].shape[1]): + vals[name][i][j] = _vals.GetTuple(i)[j] + return pts, elems, vals