Source code for pygimli.meshtools.grid

# -*- coding: utf-8 -*-
"""General grid generation and maintenance."""

import numpy as np

import pygimli as pg


[docs] def createGrid(x=None, y=None, z=None, **kwargs): """Create grid style mesh. Generate simple grid with defined node positions for each dimension. The resulting grid depends on the amount of given coordinate arguments and consists out of edges (1D - x), quads (2D- x and y), or hexahedrons(3D- x, y, and z). For grids with world boundary markers (-1 surface and -2 subsurface) the y or z array need to be in increasing order. Parameters ---------- kwargs: x: array x-coordinates for all Nodes (1D, 2D, 3D) y: array y-coordinates for all Nodes (2D, 3D) z: array z-coordinates for all Nodes (3D) marker: int = 0 Marker for resulting cells. worldBoundaryMarker : bool = False Boundaries are enumerated with world marker, i.e., Top = -1 All remaining = -2. Default markers: left=1, right=2, top=3, bottom=4, front=5, back=6 Returns ------- :gimliapi:`GIMLI::Mesh` Either 1D, 2D or 3D mesh depending the input. Examples -------- >>> import pygimli as pg >>> mesh = pg.meshtools.createGrid(x=[0, 1, 1.5, 2], y=[-1, -.5, -.25, 0], ... marker=2) >>> print(mesh) Mesh: Nodes: 16 Cells: 9 Boundaries: 24 >>> fig, axs = pg.plt.subplots(1, 2) >>> _ = pg.show(mesh, markers=True, showMesh=True, ax=axs[0]) >>> mesh = pg.meshtools.createGrid(x=[0, 1, 1.5, 2], y=[-1, -.5, -0.25, 0], ... worldBoundaryMarker=True, marker=2) >>> print(mesh) Mesh: Nodes: 16 Cells: 9 Boundaries: 24 >>> _ = pg.show(mesh, markers=True, showBoundaries=True, ... showMesh=True, ax=axs[1]) """ if 'degree' in kwargs: return createGridPieShaped(x, **kwargs) if x is not None: if isinstance(x, int): x = list(range(x)) kwargs['x'] = x if y is not None: if isinstance(y, int): y = list(range(y)) kwargs['y'] = y if z is not None: if isinstance(z, int): z = list(range(z)) kwargs['z'] = z return pg.core.createGrid(**kwargs)
[docs] def createGridPieShaped(x, degree=10.0, h=2, marker=0): """Create a 2D pie shaped grid (segment from annulus or cirlce). TODO: ---- * degree: > 90 .. 360 Arguments --------- x: array x-coordinates for all Nodes (2D). If you need it 3D, you can apply :py:mod:`pygimli.meshtools.extrudeMesh` on it. degree: float [None] Create a pie shaped grid for a value between 0 and 90. Creates an optional inner boundary (marker=2) for a annulus with x[0] > 0. Outer boundary marker is 1. Optional h refinement. Center node is the first for circle segment. h: int [2] H-Refinement for degree option. marker: int = 0 Marker for resulting cells. Returns ------- mesh: :gimliapi:`GIMLI::Mesh` Examples -------- >>> import pygimli as pg >>> mesh = pg.meshtools.createGridPieShaped(x=[0, 1, 3], degree=45, h=3) >>> print(mesh) Mesh: Nodes: 117 Cells: 128 Boundaries: 244 >>> _ = pg.show(mesh) >>> mesh = pg.meshtools.createGridPieShaped(x=[1, 2, 3], degree=45, h=3) >>> print(mesh) Mesh: Nodes: 153 Cells: 128 Boundaries: 280 >>> _ = pg.show(mesh) """ mesh = pg.Mesh(dim=2) for i in range(0, len(x)): mesh.createNodeWithCheck([x[i], 0.0]) mesh.createNodeWithCheck([x[i]*np.cos(degree*np.pi/180), x[i]*np.sin(degree*np.pi/180)]) if abs(x[0]) < 1e-6: mesh.createCell([0, 1, 2]) for i in range(0, (len(x)-2)*2-1, 2): c = mesh.createCell([i+1, i+3, i+4, i+2]) else: for i in range(0, len(x)*2-2, 2): c = mesh.createCell([i, i+2, i+3, i+1]) mesh.createBoundary([0, 1], marker=1) mesh.createBoundary([mesh.nodeCount()-2, mesh.nodeCount()-1], marker=2) for i in range(h): mesh = mesh.createH2() mesh.createNeighbourInfos() for b in mesh.boundaries(): if b.outside() and b.marker() == 0: if b.norm()[1] == 0.0: b.setMarker(4) # bottom else: b.setMarker(3) meshR = pg.Mesh(mesh) # move all nodes on the inner boundary to rw for b in mesh.boundaries(): line = pg.Line(b.node(0).pos(), b.node(1).pos()) rSoll = line.intersect([0.0, 0.0], [1.0, 0.0])[0] if rSoll > 1e-4: for n in b.nodes(): scale = rSoll/n.pos().abs() if scale > 1: meshR.node(n.id()).setPos(pg.Line([0.0, 0.0], n.pos()).at(scale)) if marker != 0: for c in meshR.cells(): c.setMarker(marker) return meshR
[docs] def appendBoundary(mesh, **kwargs): """Append Boundary to a given mesh. Syntactic sugar for :py:mod:`pygimli.meshtools.appendTriangleBoundary` and :py:mod:`pygimli.meshtools.appendTetrahedronBoundary`. Parameters ---------- mesh: :gimliapi:`GIMLI::Mesh` "2d or 3d Mesh to which the boundary will be appended. Keyword Args ------------ **kwargs forwarded to :py:mod:`pygimli.meshtools.appendTriangleBoundary` or :py:mod:`pygimli.meshtools.appendTetrahedronBoundary`. Returns ------- :gimliapi:`GIMLI::Mesh` A new 2D or 3D mesh containing the original mesh and a boundary around. """ if mesh.dim() == 2: return appendTriangleBoundary(mesh, **kwargs) elif mesh.dim() == 3: return appendTetrahedronBoundary(mesh, **kwargs) pg.critical("Don't know how to append boundary to: ", mesh)
[docs] def appendTriangleBoundary(mesh, xbound=10, ybound=10, marker=1, isSubSurface=True, **kwargs): """Add a triangle mesh boundary to a given mesh. Returns a new mesh that contains a triangulated box around a given mesh suitable for geo-simulation (surface boundary with marker == -1 at the top and marker == -2 in the inner subsurface). The old boundary marker from the input mesh are preserved, except for marker == -2 which will be changed to +2 as we assume marker == -2 is the world marker for outer boundaries in the subsurface. Note, this all will only work stable if the mesh generator (triangle) preserve all input boundaries. However this will lead to bad quality meshes for the boundary region so its a good idea to play with the addNodes keyword argument to manually refine the newly created outer boundaries. Parameters ---------- mesh: :gimliapi:`GIMLI::Mesh` Mesh to which the triangle boundary should be appended. xbound: float, optional Absolute horizontal prolongation distance. Need to be greater 2. ybound: float, optional Absolute vertical prolongation distance. marker: int, optional Marker of new cells. isSubSurface: boolean [True] Apply boundary conditions suitable for geo-simulation and prolongate mesh to the surface if necessary. Keyword Args ------------ ** kargs forwarded to pg.createMesh quality : float, optional Triangle quality. area: float, optional Triangle max size within the boundary. smooth : boolean, optional Apply mesh smoothing. addNodes : int[5], iterable Add additional nodes on the outer boundaries. Or for each boundary if given 5 values (isSubsurface=True) or 4 for isSubsurface=False Returns ------- :gimliapi:`GIMLI::Mesh` A new 2D mesh containing the original mesh and a boundary arround. See Also -------- :py:mod:`pygimli.meshtools.appendBoundary` :py:mod:`pygimli.meshtools.appendTetrahedronBoundary` Examples -------- >>> import matplotlib.pyplot as plt >>> import pygimli as pg >>> from pygimli.viewer.mpl import drawMesh, drawModel >>> import pygimli.meshtools as mt >>> inner = pg.createGrid(range(5), range(5), marker=1) >>> fig, axs = plt.subplots(2,3) >>> ax, _ = pg.show(inner, markers=True, showBoundaries=True, showMesh=True, ax=axs[0][0]) >>> m = mt.appendTriangleBoundary(inner, xbound=3, ybound=3, marker=2, addNodes=0, isSubSurface=False) >>> ax, _ = pg.show(m, markers=True, showBoundaries=True, showMesh=True, ax=axs[0][1]) >>> m = mt.appendTriangleBoundary(inner, xbound=4, ybound=1, marker=2, addNodes=5, isSubSurface=False) >>> ax, _ = pg.show(m, markers=True, showBoundaries=True, showMesh=True, ax=axs[0][2]) >>> m = mt.appendTriangleBoundary(inner, xbound=4, ybound=4, marker=2, addNodes=0, isSubSurface=True) >>> ax, _ = pg.show(m, markers=True, showBoundaries=True, showMesh=True, ax=axs[1][0]) >>> m = mt.appendTriangleBoundary(inner, xbound=4, ybound=4, marker=2, addNodes=5, isSubSurface=True) >>> ax, _ = pg.show(m, markers=True, showBoundaries=True, showMesh=True, ax=axs[1][1]) >>> surf = mt.createPolygon([[0, 0],[5, 3], [10, 1]], boundaryMarker=-1, addNodes=5, interpolate='spline') >>> m = mt.appendTriangleBoundary(surf, xbound=4, ybound=4, marker=2, addNodes=5, isSubSurface=True) >>> ax, _ = pg.show(m, markers=True, showBoundaries=True, showMesh=True, ax=axs[1][2]) """ poly = pg.Mesh(isGeometry=True) if isSubSurface: bs = mesh.findBoundaryByMarker(pg.core.MARKER_BOUND_HOMOGEN_NEUMANN) if len(bs) == 0: for b in mesh.boundaries(): if b.outside() and b.norm()[1] == 1.0: bs.append(b) paths = mesh.findPaths(bs) if len(paths) > 0: startPoint = mesh.node(paths[0][0]).pos() endPoint = mesh.node(paths[0][-1]).pos() if startPoint[0] > endPoint[0]: startPoint = endPoint endPoint = mesh.node(paths[0][0]).pos() else: pg.critical("Can't identify upper part of the mesh to be moved to" "the surface. Maybe define them with Marker==-1") addNodes = kwargs.pop('addNodes', 5) boundPoly = [pg.Pos(startPoint)] if isinstance(addNodes, (float, int)) and addNodes > 0: addNodes = np.full(5, addNodes) if hasattr(addNodes, '__len__') and len(addNodes) == 5: boundPoly.extend([boundPoly[-1] - pg.Pos(x, 0) for x in pg.utils.grange(1, xbound, n=addNodes[0]+1, log=True)]) boundPoly.extend([boundPoly[-1] - pg.Pos(0, y) for y in np.linspace( 0, mesh.ymax() - mesh.ymin() + ybound, addNodes[1] + 1)[1:]]) boundPoly.extend( [boundPoly[-1] + pg.Pos(x, 0) for x in np.linspace(0, (endPoint-startPoint)[0] + 2*xbound, addNodes[2]+1)[1:]]) boundPoly.extend( [boundPoly[-1] + pg.Pos(0, y) for y in np.linspace(0, endPoint[1]-boundPoly[-1][1], addNodes[3]+1)[1:]]) boundPoly.extend( [boundPoly[-1] - pg.Pos(xbound-x, 0) for x in pg.utils.grange(1, xbound, n=addNodes[4]+1, log=True)[::-1][1:]]) else: boundPoly.append(boundPoly[-1] - pg.Pos(xbound, 0)) boundPoly.append(boundPoly[-1] - pg.Pos(0, mesh.ymax() - mesh.ymin() + ybound)) boundPoly.append(boundPoly[-1] + pg.Pos((endPoint-startPoint)[0] + 2*xbound, 0)) boundPoly.append(pg.Pos(endPoint) + pg.Pos(xbound, 0)) boundPoly.append(pg.Pos(endPoint)) poly = pg.meshtools.createPolygon(boundPoly, isClosed=False) poly.addRegionMarker(pg.Pos([poly.xmin(), poly.ymin()]) + [xbound/100, ybound/100], marker=marker) if mesh.cellCount() > 0: poly.addHoleMarker(pg.Pos([mesh.xmin(), mesh.ymin()]) + [0.001, 0.001]) else: # no isSubSurface boundPoly = [[mesh.xmin() - xbound, mesh.ymin() - ybound], [mesh.xmin() - xbound, mesh.ymax() + ybound], [mesh.xmax() + xbound, mesh.ymax() + ybound], [mesh.xmax() + xbound, mesh.ymin() - ybound]] poly = pg.meshtools.createPolygon(boundPoly, isClosed=True, marker=marker, addNodes=kwargs.pop('addNodes', 5)) for b in mesh.boundaries(): if b.outside() or b.marker() == -1: poly.copyBoundary(b) preserveSwitch = 'Y' #pg.show(poly, fillRegion=False) #pg.show(poly, boundaryMarkers=True, showNodes=True) #pg.wait() mesh2 = pg.meshtools.createMesh(poly, preserveBoundary=preserveSwitch, **kwargs) # pg.show(mesh2, boundaryMarkers=True, showNodes=True) # start extracting all cells with marker from mesh2 and all orginal cells mesh3 = pg.Mesh(2) for c in mesh2.cells(): if c.marker() == marker: mesh3.copyCell(c) # ! map copies the cell not the reference, this should not happen # **TODO check 20210305 # map(lambda cell: mesh2.copyCell(cell), mesh2.cells()) for c in mesh.cells(): mesh3.copyCell(c) # we need to delete the old boundary markers or the new neighbour infos # will fail for old outside boundaries mesh3.setBoundaryMarkers(np.zeros(mesh3.boundaryCount())) mesh3.createNeighborInfos(force=True) for b in mesh.boundaries(): if b.marker() != 0: b2 = mesh3.copyBoundary(b) # some automagic: original mesh contains bmarker == -2 which means # mixed condition, this special marker will be switched to 2 if b.marker() == -2: b2.setMarker(2) for b in mesh3.boundaries(): if b.outside() and b.marker() > -1: if b.norm().x() != 0 or b.norm().y() == -1.0 or not isSubSurface: b.setMarker(pg.core.MARKER_BOUND_MIXED) else: b.setMarker(pg.core.MARKER_BOUND_HOMOGEN_NEUMANN) return mesh3
[docs] def appendBoundaryGrid(grid, xbound=None, ybound=None, zbound=None, marker=1, isSubSurface=True, **kwargs): """Return a copy of grid surrounded by a boundary grid. Note, the input grid needs to be a 2d or 3d grid with quad/hex cells. TODO ---- * preserve inner boundaries * add subsurface setting Args ---- grid: :gimliapi:`GIMLI::Mesh` 2D or 3D Mesh that must contain structured quads or hex cells xbound: iterable of type float [None] Needed for 2D or 3D grid prolongation and will be added on the left side in opposit order and on the right side in normal order. ybound: iterable of type float [None] Needed for 2D or 3D grid prolongation and will be added (2D bottom, 3D front) in opposit order and (2D top, 3D back) in normal order. zbound: iterable of type float [None] Needed for 3D grid prolongation and will be added the bottom side in opposite order on the top side in normal order. marker: int [1] Cellmarker for the cells in the boundary region isSubSurface : boolean, optional Apply boundary conditions suitable for geo-simulaion and prolongate mesh to the surface if necessary, e.i., no boundary on top of the grid. Examples -------- >>> import pygimli as pg >>> import pygimli.meshtools as mt >>> grid = mt.createGrid(5,5) ... >>> g1 = mt.appendBoundaryGrid(grid, ... xbound=[1, 3, 6], ... ybound=[1, 3, 6], ... marker=2, ... isSubSurface=False) >>> ax,_ = pg.show(g1, markers=True, showMesh=True) >>> grid = mt.createGrid(5,5,5) ... >>> g2 = mt.appendBoundaryGrid(grid, ... xbound=[1, 3, 6], ... ybound=[1, 3, 6], ... zbound=[1, 3, 6], ... marker=2, ... isSubSurface=False) >>> ax, _ = pg.show(g2, g2.cellMarkers(), showMesh=True, ... filter={'clip':{}}); """ if isSubSurface: pg.critical('Implement me') def _concat(v, vBound): if (not pg.isArray(vBound)): pg.critical("please give bound array") v = np.append(-np.array(vBound)[::-1] + v[0], v) v = np.append(v, v[-1] + np.array(vBound)) return v x = None y = None z = None if grid.dim() > 1: if grid.dim() == 2: if any([c.nodeCount() != 4 for c in grid.cells()]): pg.critical("Grid have other cells than quads. " "Can't refine it with a grid") x = pg.utils.unique(pg.x(grid)) y = pg.utils.unique(pg.y(grid)) x = _concat(x, xbound) y = _concat(y, ybound) if grid.dim() == 3: if any([c.nodeCount() != 8 for c in grid.cells()]): pg.critical("Grid have other cells than hex's. " "Can't refine it with a grid") z = pg.utils.unique(pg.z(grid)) z = _concat(z, zbound) mesh = pg.meshtools.createGrid(x=x, y=y, z=z, marker=marker) mesh.setCellMarkers(pg.interpolate(grid, grid.cellMarkers(), mesh.cellCenters(), fallback=marker)) return mesh
[docs] def appendTetrahedronBoundary(mesh, xbound=10, ybound=10, zbound=10, marker=1, isSubSurface=True, **kwargs): """Return a copy of mesh surrounded by a tetrahedron mesh as boundary. Returns a new mesh that contains a tetrahedron mesh box around a given mesh suitable for geo-simulation (surface boundary with marker = -1 at top and marker = -2 in the inner subsurface). The old boundary marker from mesh will be preserved, except for marker == -2 which will be switched to 2 as we assume -2 is the world marker for outer boundaries in the subsurface. Note ---- This method will only work stable if the mesh generator (Tetgen) preserves all input boundaries. This will lead to bad quality meshes for the boundary region so its a good idea to play with the addNodes keword argument to manually refine the newly created outer boundaries. If the input mesh consists of hexahedrons a small inconsistency will arise because a quad boundary element will be split by 2 triangle boundaries from the boundary tetrahedrons. The effect of this hanging edges are unclear, also createNeighbourInfos may fail. We need to implement/test pyramid cells to handle this. TODO ---- * set correct boundary conditions * isSubSurface * pyramid cells as connecting cells * need for preserve Boundary check * preserve Boundary support * addNodes support Parameters ---------- mesh: :gimliapi:`GIMLI::Mesh` 3D Mesh to which the tetrahedron boundary should be appended. xbound: float [10] Horizontal prolongation distance in meter at x-direction. Need to be >= 0. ybound: float [10] Horizonal prolongation distance in meter at y-direction. Need to be greater 0. zbound: float [10] Vertical prolongation distance in meter at z-direction (>0). marker: int, optional Marker of new cells. addNodes: float, optional Triangle quality. isSubSurface : boolean, optional Apply boundary conditions suitable for geo-simulaion and prolongate mesh to the surface if necessary. verbose : boolean, optional Be verbose. Returns ------- :gimliapi:`GIMLI::Mesh` A new 3D mesh containing the original mesh and a boundary arround. See Also -------- :py:mod:`pygimli.meshtools.appendBoundary`, :py:mod:`pygimli.meshtools.appendTriangleBoundary` Examples -------- >>> import pygimli as pg >>> import pygimli.meshtools as mt >>> grid = mt.createGrid(5,5,5) ... >>> mesh = mt.appendBoundary(grid, xbound=5, ybound=5, zbound=5, ... isSubSurface=False) >>> ax, _ = pg.show(mesh, mesh.cellMarkers(), showMesh=True, ... filter={'clip':{}}) """ if isSubSurface: pg.critical('Implement me') meshBoundary = pg.Mesh(3, isGeometry=True) for b in mesh.boundaries(): if b.outside() or b.marker() == -1: meshBoundary.copyBoundary(b) bb = meshBoundary.bb() meshBoundary.addHoleMarker(bb[0] + (bb[1]-bb[0])/1000.) if not any([xbound > 0, ybound > 0, zbound > 0]): pg.critical('all boundaries need to be greater 0.') startPos = bb[0] - [xbound, ybound, zbound] endPos = bb[1] + [xbound, ybound, zbound] boundaryBox = pg.meshtools.createCube(start=startPos, end=endPos) boundMesh = pg.meshtools.createMesh(boundaryBox + meshBoundary) boundMesh.setCellMarkers(np.ones(boundMesh.cellCount()) * marker) outMesh = pg.Mesh(mesh) for c in boundMesh.cells(): outMesh.copyCell(c) return outMesh
if __name__ == "__main__": pass