def CubedSphereMesh(radius, refinement_level=0, degree=1, reorder=None, comm=COMM_WORLD): """Generate an cubed approximation to the surface of the sphere. :arg radius: The radius of the sphere to approximate. :kwarg refinement_level: optional number of refinements (0 is a cube). :kwarg degree: polynomial degree of coordinate space (defaults to 1: bilinear quads) :kwarg reorder: (optional), should the mesh be reordered? """ if refinement_level < 0 or refinement_level % 1: raise RuntimeError("Number of refinements must be a non-negative integer") if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") cells, coords = _cubedsphere_cells_and_coords(radius, refinement_level) plex = mesh._from_cell_list(2, cells, coords, comm) m = mesh.Mesh(plex, dim=3, reorder=reorder) if degree > 1: new_coords = function.Function(functionspace.VectorFunctionSpace(m, "Q", degree)) new_coords.interpolate(ufl.SpatialCoordinate(m)) # "push out" to sphere new_coords.dat.data[:] *= (radius / np.linalg.norm(new_coords.dat.data, axis=1)).reshape(-1, 1) m = mesh.Mesh(new_coords) m._radius = radius return m
def IcosahedralSphereMesh(radius, refinement_level=0, degree=1, reorder=None): """Generate an icosahedral approximation to the surface of the sphere. :arg radius: The radius of the sphere to approximate. For a radius R the edge length of the underlying icosahedron will be. .. math:: a = \\frac{R}{\\sin(2 \\pi / 5)} :kwarg refinement_level: optional number of refinements (0 is an icosahedron). :kwarg degree: polynomial degree of coordinate space (defaults to 1: flat triangles) :kwarg reorder: (optional), should the mesh be reordered? """ if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") from math import sqrt phi = (1 + sqrt(5)) / 2 # vertices of an icosahedron with an edge length of 2 vertices = np.array([[-1, phi, 0], [1, phi, 0], [-1, -phi, 0], [1, -phi, 0], [0, -1, phi], [0, 1, phi], [0, -1, -phi], [0, 1, -phi], [phi, 0, -1], [phi, 0, 1], [-phi, 0, -1], [-phi, 0, 1]]) # faces of the base icosahedron faces = np.array( [[0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11], [1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8], [3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9], [4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1]], dtype=np.int32) plex = mesh._from_cell_list(2, faces, vertices) plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() coords = plex.getCoordinatesLocal().array.reshape(-1, 3) scale = (radius / np.linalg.norm(coords, axis=1)).reshape(-1, 1) coords *= scale m = mesh.Mesh(plex, dim=3, reorder=reorder) if degree > 1: new_coords = function.Function( functionspace.VectorFunctionSpace(m, "CG", degree)) new_coords.interpolate(expression.Expression(("x[0]", "x[1]", "x[2]"))) # "push out" to sphere new_coords.dat.data[:] *= ( radius / np.linalg.norm(new_coords.dat.data, axis=1)).reshape( -1, 1) m = mesh.Mesh(new_coords) m._icosahedral_sphere = radius return m
def PeriodicIntervalMesh(ncells, length): """Generate a periodic mesh of an interval. :arg ncells: The number of cells over the interval. :arg length: The length the interval.""" if ncells < 3: raise ValueError("1D periodic meshes with fewer than 3 \ cells are not currently supported") m = CircleManifoldMesh(ncells) coord_fs = VectorFunctionSpace(m, 'DG', 1, dim=1) old_coordinates = m.coordinates new_coordinates = Function(coord_fs) periodic_kernel = """double Y,pi; Y = 0.5*(old_coords[0][1]-old_coords[1][1]); pi=3.141592653589793; for(int i=0;i<2;i++){ new_coords[i][0] = atan2(old_coords[i][1],old_coords[i][0])/pi/2; if(new_coords[i][0]<0.) new_coords[i][0] += 1; if(new_coords[i][0]==0 && Y<0.) new_coords[i][0] = 1.0; new_coords[i][0] *= L[0]; }""" cL = Constant(length) par_loop(periodic_kernel, dx, {"new_coords": (new_coordinates, WRITE), "old_coords": (old_coordinates, READ), "L": (cL, READ)}) return mesh.Mesh(new_coordinates)
def CircleManifoldMesh(ncells, radius=1, comm=COMM_WORLD): """Generated a 1D mesh of the circle, immersed in 2D. :arg ncells: number of cells the circle should be divided into (min 3) :kwarg radius: (optional) radius of the circle to approximate (defaults to 1). :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). """ if ncells < 3: raise ValueError("CircleManifoldMesh must have at least three cells") vertices = radius * np.column_stack( (np.cos(np.arange(ncells) * (2 * np.pi / ncells)), np.sin(np.arange(ncells) * (2 * np.pi / ncells)))) cells = np.column_stack((np.arange(0, ncells, dtype=np.int32), np.roll(np.arange(0, ncells, dtype=np.int32), -1))) plex = mesh._from_cell_list(1, cells, vertices, comm) m = mesh.Mesh(plex, dim=2, reorder=False) m._circle_manifold = radius return m
def TorusMesh(nR, nr, R, r, quadrilateral=False, reorder=None): """Generate a toroidal mesh :arg nR: The number of cells in the major direction (min 3) :arg nr: The number of cells in the minor direction (min 3) :arg R: The major radius :arg r: The minor radius :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered """ if nR < 3 or nr < 3: raise ValueError("Must have at least 3 cells in each direction") # gives an array [[0, 0], [0, 1], ..., [1, 0], [1, 1], ...] idx_temp = np.asarray(np.meshgrid(np.arange(nR), np.arange(nr))).swapaxes(0, 2).reshape(-1, 2) # vertices - standard formula for (x, y, z), see Wikipedia vertices = np.column_stack(( (R + r*np.cos(idx_temp[:, 1]*(2*np.pi/nr)))*np.cos(idx_temp[:, 0]*(2*np.pi/nR)), (R + r*np.cos(idx_temp[:, 1]*(2*np.pi/nr)))*np.sin(idx_temp[:, 0]*(2*np.pi/nR)), r*np.sin(idx_temp[:, 1]*(2*np.pi/nr)))) # cell vertices i, j = np.meshgrid(np.arange(nR), np.arange(nr)) i = i.reshape(-1) # Miklos's suggestion to make the code j = j.reshape(-1) # less impenetrable cells = [i*nr + j, i*nr + (j+1) % nr, ((i+1) % nR)*nr + (j+1) % nr, ((i+1) % nR)*nr + j] cells = np.column_stack(cells) if not quadrilateral: # two cells per cell above... cells = cells[:, [0, 1, 3, 1, 2, 3]].reshape(-1, 3) plex = mesh._from_cell_list(2, cells, vertices) m = mesh.Mesh(plex, dim=3, reorder=reorder) return m
def RectangleMesh(nx, ny, Lx, Ly, quadrilateral=False, reorder=None): """Generate a rectangular mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered The boundary edges in this mesh are numbered as follows: * 1: plane x == 0 * 2: plane x == Lx * 3: plane y == 0 * 4: plane y == Ly """ if quadrilateral: dx = float(Lx) / nx dy = float(Ly) / ny xcoords = np.arange(0.0, Lx + 0.01 * dx, dx) ycoords = np.arange(0.0, Ly + 0.01 * dy, dy) coords = np.asarray(np.meshgrid(xcoords, ycoords)).swapaxes(0, 2).reshape(-1, 2) # cell vertices i, j = np.meshgrid(np.arange(nx), np.arange(ny)) cells = [i*(ny+1) + j, i*(ny+1) + j+1, (i+1)*(ny+1) + j+1, (i+1)*(ny+1) + j] cells = np.asarray(cells).swapaxes(0, 2).reshape(-1, 4) plex = mesh._from_cell_list(2, cells, coords) else: boundary = PETSc.DMPlex().create(MPI.comm) boundary.setDimension(1) boundary.createSquareBoundary([0., 0.], [float(Lx), float(Ly)], [nx, ny]) boundary.setTriangleOptions("pqezQYSl") plex = PETSc.DMPlex().generate(boundary) # mark boundary facets plex.createLabel("boundary_ids") plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() xtol = float(Lx)/(2*nx) ytol = float(Ly)/(2*ny) for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) if abs(face_coords[0]) < xtol and abs(face_coords[2]) < xtol: plex.setLabelValue("boundary_ids", face, 1) if abs(face_coords[0] - Lx) < xtol and abs(face_coords[2] - Lx) < xtol: plex.setLabelValue("boundary_ids", face, 2) if abs(face_coords[1]) < ytol and abs(face_coords[3]) < ytol: plex.setLabelValue("boundary_ids", face, 3) if abs(face_coords[1] - Ly) < ytol and abs(face_coords[3] - Ly) < ytol: plex.setLabelValue("boundary_ids", face, 4) return mesh.Mesh(plex, reorder=reorder)
def PeriodicIntervalMesh(ncells, length, comm=COMM_WORLD): """Generate a periodic mesh of an interval. :arg ncells: The number of cells over the interval. :arg length: The length the interval. :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). """ if ncells < 3: raise ValueError("1D periodic meshes with fewer than 3 \ cells are not currently supported") m = CircleManifoldMesh(ncells, comm=comm) coord_fs = VectorFunctionSpace(m, 'DG', 1, dim=1) old_coordinates = m.coordinates new_coordinates = Function(coord_fs) periodic_kernel = """ const double pi = 3.141592653589793; const double eps = 1e-12; double a = atan2(old_coords[0][1], old_coords[0][0]) / (2*pi); double b = atan2(old_coords[1][1], old_coords[1][0]) / (2*pi); int swap = 0; if ( a >= b ) { const double tmp = b; b = a; a = tmp; swap = 1; } if ( fabs(b) < eps && a < -eps ) { b = 1.0; } if ( a < -eps ) { a += 1; } if ( b < -eps ) { b += 1; } if ( swap ) { const double tmp = b; b = a; a = tmp; } new_coords[0][0] = a * L[0]; new_coords[1][0] = b * L[0]; """ cL = Constant(length) par_loop( periodic_kernel, dx, { "new_coords": (new_coordinates, WRITE), "old_coords": (old_coordinates, READ), "L": (cL, READ) }) return mesh.Mesh(new_coordinates)
def UnitTetrahedronMesh(comm=COMM_WORLD): """Generate a mesh of the reference tetrahedron. :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). """ coords = [[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]] cells = [[0, 1, 2, 3]] plex = mesh._from_cell_list(3, cells, coords, comm) return mesh.Mesh(plex, reorder=False)
def BoxMesh(nx, ny, nz, Lx, Ly, Lz, reorder=None): """Generate a mesh of a 3D box. :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg nz: The number of cells in the z direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :arg Lz: The extent in the z direction :kwarg reorder: (optional), should the mesh be reordered? The boundary surfaces are numbered as follows: * 1: plane x == 0 * 2: plane x == Lx * 3: plane y == 0 * 4: plane y == Ly * 5: plane z == 0 * 6: plane z == Lz """ # Create mesh from DMPlex boundary = PETSc.DMPlex().create(MPI.comm) boundary.setDimension(2) boundary.createCubeBoundary([0., 0., 0.], [Lx, Ly, Lz], [nx, ny, nz]) plex = PETSc.DMPlex().generate(boundary) # Apply boundary IDs plex.createLabel("boundary_ids") plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() xtol = float(Lx)/(2*nx) ytol = float(Ly)/(2*ny) ztol = float(Lz)/(2*nz) for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) if abs(face_coords[0]) < xtol and abs(face_coords[3]) < xtol and abs(face_coords[6]) < xtol: plex.setLabelValue("boundary_ids", face, 1) if abs(face_coords[0] - Lx) < xtol and abs(face_coords[3] - Lx) < xtol and abs(face_coords[6] - Lx) < xtol: plex.setLabelValue("boundary_ids", face, 2) if abs(face_coords[1]) < ytol and abs(face_coords[4]) < ytol and abs(face_coords[7]) < ytol: plex.setLabelValue("boundary_ids", face, 3) if abs(face_coords[1] - Ly) < ytol and abs(face_coords[4] - Ly) < ytol and abs(face_coords[7] - Ly) < ytol: plex.setLabelValue("boundary_ids", face, 4) if abs(face_coords[2]) < ztol and abs(face_coords[5]) < ztol and abs(face_coords[8]) < ztol: plex.setLabelValue("boundary_ids", face, 5) if abs(face_coords[2] - Lz) < ztol and abs(face_coords[5] - Lz) < ztol and abs(face_coords[8] - Lz) < ztol: plex.setLabelValue("boundary_ids", face, 6) return mesh.Mesh(plex, reorder=reorder)
def IntervalMesh(ncells, length_or_left, right=None, comm=COMM_WORLD): """ Generate a uniform mesh of an interval. :arg ncells: The number of the cells over the interval. :arg length_or_left: The length of the interval (if ``right`` is not provided) or else the left hand boundary point. :arg right: (optional) position of the right boundary point (in which case ``length_or_left`` should be the left boundary point). :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). The left hand boundary point has boundary marker 1, while the right hand point has marker 2. """ if right is None: left = 0 right = length_or_left else: left = length_or_left if ncells <= 0 or ncells % 1: raise ValueError("Number of cells must be a postive integer") length = right - left if length < 0: raise ValueError("Requested mesh has negative length") dx = length / ncells # This ensures the rightmost point is actually present. coords = np.arange(left, right + 0.01 * dx, dx, dtype=np.double).reshape(-1, 1) cells = np.dstack( (np.arange(0, len(coords) - 1, dtype=np.int32), np.arange(1, len(coords), dtype=np.int32))).reshape(-1, 2) plex = mesh._from_cell_list(1, cells, coords, comm) # Apply boundary IDs plex.createLabel("boundary_ids") coordinates = plex.getCoordinates() coord_sec = plex.getCoordinateSection() vStart, vEnd = plex.getDepthStratum(0) # vertices for v in range(vStart, vEnd): vcoord = plex.vecGetClosure(coord_sec, coordinates, v) if vcoord[0] == coords[0]: plex.setLabelValue("boundary_ids", v, 1) if vcoord[0] == coords[-1]: plex.setLabelValue("boundary_ids", v, 2) return mesh.Mesh(plex, reorder=False)
def CircleMesh(radius, resolution, reorder=None): """Generate a structured triangular mesh of a circle. :arg radius: The radius of the circle. :arg resolution: The number of cells lying along the radius and the arc of the quadrant. :kwarg reorder: (optional), should the mesh be reordered? """ source = """ lc = %g; Point(1) = {0, -0.5, 0, lc}; Point(2) = {0, 0.5, 0, lc}; Line(1) = {1, 2}; surface[] = Extrude{{0, 0, %g},{0, 0, 0}, 0.9999 * Pi}{ Line{1};Layers{%d}; }; Physical Surface(2) = { surface[1] }; """ % (0.5 / resolution, radius, resolution * 4) output = _get_msh_file(source, "circle_%g_%d" % (radius, resolution), 2) return mesh.Mesh(output, reorder=reorder)
def PeriodicRectangleMesh(nx, ny, Lx, Ly, direction="both", quadrilateral=False, reorder=None, comm=COMM_WORLD): """Generate a periodic rectangular mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :arg direction: The direction of the periodicity, one of ``"both"``, ``"x"`` or ``"y"``. :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). If direction == "x" the boundary edges in this mesh are numbered as follows: * 1: plane y == 0 * 2: plane y == Ly If direction == "y" the boundary edges are: * 1: plane x == 0 * 2: plane x == Lx """ if direction == "both" and ny == 1 and quadrilateral: return OneElementThickMesh(nx, Lx, Ly) if direction not in ("both", "x", "y"): raise ValueError("Cannot have a periodic mesh with periodicity '%s'" % direction) if direction != "both": return PartiallyPeriodicRectangleMesh(nx, ny, Lx, Ly, direction=direction, quadrilateral=quadrilateral, reorder=reorder, comm=comm) if nx < 3 or ny < 3: raise ValueError("2D periodic meshes with fewer than 3 \ cells in each direction are not currently supported") m = TorusMesh(nx, ny, 1.0, 0.5, quadrilateral=quadrilateral, reorder=reorder, comm=comm) coord_fs = VectorFunctionSpace(m, 'DG', 1, dim=2) old_coordinates = m.coordinates new_coordinates = Function(coord_fs) periodic_kernel = """ double pi = 3.141592653589793; double eps = 1e-12; double bigeps = 1e-1; double phi, theta, Y, Z; Y = 0.0; Z = 0.0; for(int i=0; i<old_coords.dofs; i++) { Y += old_coords[i][1]; Z += old_coords[i][2]; } for(int i=0; i<new_coords.dofs; i++) { phi = atan2(old_coords[i][1], old_coords[i][0]); if (fabs(sin(phi)) > bigeps) theta = atan2(old_coords[i][2], old_coords[i][1]/sin(phi) - 1.0); else theta = atan2(old_coords[i][2], old_coords[i][0]/cos(phi) - 1.0); new_coords[i][0] = phi/(2.0*pi); if(new_coords[i][0] < -eps) { new_coords[i][0] += 1.0; } if(fabs(new_coords[i][0]) < eps && Y < 0.0) { new_coords[i][0] = 1.0; } new_coords[i][1] = theta/(2.0*pi); if(new_coords[i][1] < -eps) { new_coords[i][1] += 1.0; } if(fabs(new_coords[i][1]) < eps && Z < 0.0) { new_coords[i][1] = 1.0; } new_coords[i][0] *= Lx[0]; new_coords[i][1] *= Ly[0]; } """ cLx = Constant(Lx) cLy = Constant(Ly) par_loop(periodic_kernel, dx, {"new_coords": (new_coordinates, WRITE), "old_coords": (old_coordinates, READ), "Lx": (cLx, READ), "Ly": (cLy, READ)}) return mesh.Mesh(new_coordinates)
def RectangleMesh(nx, ny, Lx, Ly, quadrilateral=False, reorder=None, diagonal="left", comm=COMM_WORLD): """Generate a rectangular mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). :kwarg diagonal: For triangular meshes, should the diagonal got from bottom left to top right (``"right"``), or top left to bottom right (``"left"``). The boundary edges in this mesh are numbered as follows: * 1: plane x == 0 * 2: plane x == Lx * 3: plane y == 0 * 4: plane y == Ly """ for n in (nx, ny): if n <= 0 or n % 1: raise ValueError("Number of cells must be a postive integer") xcoords = np.linspace(0.0, Lx, nx + 1, dtype=np.double) ycoords = np.linspace(0.0, Ly, ny + 1, dtype=np.double) coords = np.asarray(np.meshgrid(xcoords, ycoords)).swapaxes(0, 2).reshape(-1, 2) # cell vertices i, j = np.meshgrid(np.arange(nx, dtype=np.int32), np.arange(ny, dtype=np.int32)) cells = [i*(ny+1) + j, i*(ny+1) + j+1, (i+1)*(ny+1) + j+1, (i+1)*(ny+1) + j] cells = np.asarray(cells).swapaxes(0, 2).reshape(-1, 4) if not quadrilateral: if diagonal == "left": idx = [0, 1, 3, 1, 2, 3] elif diagonal == "right": idx = [0, 1, 2, 0, 2, 3] else: raise ValueError("Unrecognised value for diagonal '%r'", diagonal) # two cells per cell above... cells = cells[:, idx].reshape(-1, 3) plex = mesh._from_cell_list(2, cells, coords, comm) # mark boundary facets plex.createLabel("boundary_ids") plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() xtol = Lx/(2*nx) ytol = Ly/(2*ny) for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) if abs(face_coords[0]) < xtol and abs(face_coords[2]) < xtol: plex.setLabelValue("boundary_ids", face, 1) if abs(face_coords[0] - Lx) < xtol and abs(face_coords[2] - Lx) < xtol: plex.setLabelValue("boundary_ids", face, 2) if abs(face_coords[1]) < ytol and abs(face_coords[3]) < ytol: plex.setLabelValue("boundary_ids", face, 3) if abs(face_coords[1] - Ly) < ytol and abs(face_coords[3] - Ly) < ytol: plex.setLabelValue("boundary_ids", face, 4) return mesh.Mesh(plex, reorder=reorder)
def OneElementThickMesh(ncells, Lx, Ly, comm=COMM_WORLD): """ Generate a rectangular mesh in the domain with corners [0,0] and [Lx, Ly] with ncells, that is periodic in the x-direction. :arg ncells: The number of cells in the mesh. :arg Lx: The width of the domain in the x-direction. :arg Ly: The width of the domain in the y-direction. :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). """ left = np.arange(ncells, dtype=np.int32) right = np.roll(left, -1) cells = np.array([left, left, right, right]).T dx = Lx/ncells X = np.arange(1.0*ncells, dtype=np.double)*dx Y = 0.*X coords = np.array([X, Y]).T # a line of coordinates, with a looped topology plex = mesh._from_cell_list(2, cells, coords, comm) mesh1 = mesh.Mesh(plex) mesh1.topology.init() cell_numbering = mesh1._cell_numbering cell_range = plex.getHeightStratum(0) cell_closure = np.zeros((cell_range[1], 9), dtype=IntType) # Get the coordinates for this process coords = plex.getCoordinatesLocal().array_r # get the PETSc section coords_sec = plex.getCoordinateSection() for e in range(*cell_range): closure, orient = plex.getTransitiveClosure(e) # get the row for this cell row = cell_numbering.getOffset(e) # run some checks assert(closure[0] == e) assert len(closure) == 7, closure edge_range = plex.getHeightStratum(1) assert(all(closure[1:5] >= edge_range[0])) assert(all(closure[1:5] < edge_range[1])) vertex_range = plex.getHeightStratum(2) assert(all(closure[5:] >= vertex_range[0])) assert(all(closure[5:] < vertex_range[1])) # enter the cell number cell_closure[row][8] = e # Get a list of unique edges edge_set = list(set(closure[1:5])) # there are two vertices in the cell cell_vertices = closure[5:] cell_X = np.array([0., 0.]) for i, v in enumerate(cell_vertices): cell_X[i] = coords[coords_sec.getOffset(v)] # Add in the edges for i in range(3): # count up how many times each edge is repeated repeats = list(closure[1:5]).count(edge_set[i]) if repeats == 2: # we have a y-periodic edge cell_closure[row][6] = edge_set[i] cell_closure[row][7] = edge_set[i] elif repeats == 1: # in this code we check if it is a right edge, or a left edge # by inspecting the x coordinates of the edge vertex (1) # and comparing with the x coordinates of the cell vertices (2) # there is only one vertex on the edge in this case edge_vertex = plex.getCone(edge_set[i])[0] # get X coordinate for this edge edge_X = coords[coords_sec.getOffset(edge_vertex)] # get X coordinates for this cell if(cell_X.min() < dx/2): if cell_X.max() < 3*dx/2: # We are in the first cell if(edge_X.min() < dx/2): # we are on left hand edge cell_closure[row][4] = edge_set[i] else: # we are on right hand edge cell_closure[row][5] = edge_set[i] else: # We are in the last cell if(edge_X.min() < dx/2): # we are on right hand edge cell_closure[row][5] = edge_set[i] else: # we are on left hand edge cell_closure[row][4] = edge_set[i] else: if(abs(cell_X.min()-edge_X.min()) < dx/2): # we are on left hand edge cell_closure[row][4] = edge_set[i] else: # we are on right hand edge cell_closure[row][5] = edge_set[i] # Add in the vertices vertices = closure[5:] v1 = vertices[0] v2 = vertices[1] x1 = coords[coords_sec.getOffset(v1)] x2 = coords[coords_sec.getOffset(v2)] # Fix orientations if(x1 > x2): if(x1 - x2 < dx*1.5): # we are not on the rightmost cell and need to swap v1, v2 = v2, v1 elif(x2 - x1 > dx*1.5): # we are on the rightmost cell and need to swap v1, v2 = v2, v1 cell_closure[row][0:4] = [v1, v1, v2, v2] mesh1.topology.cell_closure = np.array(cell_closure, dtype=IntType) mesh1.init() Vc = VectorFunctionSpace(mesh1, 'DQ', 1) fc = Function(Vc).interpolate(mesh1.coordinates) mash = mesh.Mesh(fc) topverts = Vc.cell_node_list[:, 1::2].flatten() mash.coordinates.dat.data_with_halos[topverts, 1] = Ly # search for the last cell mcoords_ro = mash.coordinates.dat.data_ro_with_halos mcoords = mash.coordinates.dat.data_with_halos for e in range(*cell_range): cell = cell_numbering.getOffset(e) cell_nodes = Vc.cell_node_list[cell, :] Xvals = mcoords_ro[cell_nodes, 0] if(Xvals.max() - Xvals.min() > Lx/2): mcoords[cell_nodes[2:], 0] = Lx else: mcoords local_facet_dat = mash.topology.interior_facets.local_facet_dat local_facet_number = mash.topology.interior_facets.local_facet_number lfd_ro = local_facet_dat.data_ro for i in range(lfd_ro.shape[0]): if all(lfd_ro[i, :] == np.array([3, 3])): local_facet_dat.data[i, :] = [2, 3] local_facet_number[i, :] = [2, 3] return mash
def PartiallyPeriodicRectangleMesh(nx, ny, Lx, Ly, direction="x", quadrilateral=False, reorder=None, comm=COMM_WORLD): """Generates RectangleMesh that is periodic in the x or y direction. :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :kwarg direction: The direction of the periodicity (default x). :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). If direction == "x" the boundary edges in this mesh are numbered as follows: * 1: plane y == 0 * 2: plane y == Ly If direction == "y" the boundary edges are: * 1: plane x == 0 * 2: plane x == Lx """ if direction not in ("x", "y"): raise ValueError("Unsupported periodic direction '%s'" % direction) # handle x/y directions: na, La are for the periodic axis na, nb, La, Lb = nx, ny, Lx, Ly if direction == "y": na, nb, La, Lb = ny, nx, Ly, Lx if na < 3: raise ValueError("2D periodic meshes with fewer than 3 \ cells in each direction are not currently supported") m = CylinderMesh(na, nb, 1.0, 1.0, longitudinal_direction="z", quadrilateral=quadrilateral, reorder=reorder, comm=comm) coord_fs = VectorFunctionSpace(m, 'DG', 1, dim=2) old_coordinates = m.coordinates new_coordinates = Function(coord_fs) # make x-periodic mesh # unravel x coordinates like in periodic interval # set y coordinates to z coordinates periodic_kernel = """double Y,pi; Y = 0.0; for(int i=0; i<old_coords.dofs; i++) { Y += old_coords[i][1]; } pi=3.141592653589793; for(int i=0;i<new_coords.dofs;i++){ new_coords[i][0] = atan2(old_coords[i][1],old_coords[i][0])/pi/2; if(new_coords[i][0]<0.) new_coords[i][0] += 1; if(new_coords[i][0]==0 && Y<0.) new_coords[i][0] = 1.0; new_coords[i][0] *= Lx[0]; new_coords[i][1] = old_coords[i][2]*Ly[0]; }""" cLx = Constant(La) cLy = Constant(Lb) par_loop(periodic_kernel, dx, {"new_coords": (new_coordinates, WRITE), "old_coords": (old_coordinates, READ), "Lx": (cLx, READ), "Ly": (cLy, READ)}) if direction == "y": # flip x and y coordinates operator = np.asarray([[0, 1], [1, 0]]) new_coordinates.dat.data[:] = np.dot(new_coordinates.dat.data, operator.T) return mesh.Mesh(new_coordinates)
def CylinderMesh(nr, nl, radius=1, depth=1, longitudinal_direction="z", quadrilateral=False, reorder=None, comm=COMM_WORLD): """Generates a cylinder mesh. :arg nr: number of cells the cylinder circumference should be divided into (min 3) :arg nl: number of cells along the longitudinal axis of the cylinder :kwarg radius: (optional) radius of the cylinder to approximate (default 1). :kwarg depth: (optional) depth of the cylinder to approximate (default 1). :kwarg longitudinal_direction: (option) direction for the longitudinal axis of the cylinder. :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). The boundary edges in this mesh are numbered as follows: * 1: plane l == 0 (bottom) * 2: plane l == depth (top) """ if nr < 3: raise ValueError("CylinderMesh must have at least three cells") coord_xy = radius*np.column_stack((np.cos(np.arange(nr)*(2*np.pi/nr)), np.sin(np.arange(nr)*(2*np.pi/nr)))) coord_z = depth*np.linspace(0.0, 1.0, nl + 1).reshape(-1, 1) vertices = np.asarray(np.column_stack((np.tile(coord_xy, (nl + 1, 1)), np.tile(coord_z, (1, nr)).reshape(-1, 1))), dtype=np.double) # intervals on circumference ring_cells = np.column_stack((np.arange(0, nr, dtype=np.int32), np.roll(np.arange(0, nr, dtype=np.int32), -1))) # quads in the first layer ring_cells = np.column_stack((ring_cells, np.roll(ring_cells, 1, axis=1) + nr)) offset = np.arange(nl, dtype=np.int32)*nr cells = np.row_stack((ring_cells + i for i in offset)) if not quadrilateral: # two cells per cell above... cells = cells[:, [0, 1, 3, 1, 2, 3]].reshape(-1, 3) if longitudinal_direction == "x": rotation = np.asarray([[0, 0, 1], [0, 1, 0], [-1, 0, 0]], dtype=np.double) vertices = np.dot(vertices, rotation.T) elif longitudinal_direction == "y": rotation = np.asarray([[1, 0, 0], [0, 0, 1], [0, -1, 0]], dtype=np.double) vertices = np.dot(vertices, rotation.T) elif longitudinal_direction != "z": raise ValueError("Unknown longitudinal direction '%s'" % longitudinal_direction) plex = mesh._from_cell_list(2, cells, vertices, comm) plex.createLabel("boundary_ids") plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() eps = depth/(2*nl) for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) # index of x/y/z coordinates of the face element axis_ix = {"x": 0, "y": 1, "z": 2} i = axis_ix[longitudinal_direction] j = i + 3 if abs(face_coords[i]) < eps and abs(face_coords[j]) < eps: # bottom of cylinder plex.setLabelValue("boundary_ids", face, 1) if abs(face_coords[i] - depth) < eps and abs(face_coords[j] - depth) < eps: # top of cylinder plex.setLabelValue("boundary_ids", face, 2) m = mesh.Mesh(plex, dim=3, reorder=reorder) return m
def __init__(self, m, refinement_levels, refinements_per_level=1, reorder=None): """Build a hierarchy of meshes by uniformly refining a coarse mesh. :arg m: the coarse :func:`~.Mesh` to refine :arg refinement_levels: the number of levels of refinement :arg refinements_per_level: Optional number of refinements per level in the resulting hierarchy. Note that the intermediate meshes are still kept, but iteration over the mesh hierarchy skips them. :arg reorder: optional flag indicating whether to reorder the refined meshes. """ from firedrake.citations import Citations Citations().register("Mitchell2016") if m.ufl_cell().cellname() not in ["triangle", "interval"]: raise NotImplementedError( "Only supported on intervals and triangles") if refinements_per_level < 1: raise ValueError( "Must provide positive number of refinements per level") m._plex.setRefinementUniform(True) dm_hierarchy = [] cdm = m._plex self.comm = m.comm fpoint_ises = [] if m.comm.size > 1 and m._grown_halos: raise RuntimeError( "Cannot refine parallel overlapped meshes (make sure the MeshHierarchy is built immediately after the Mesh)" ) for i in range(refinement_levels * refinements_per_level): rdm = cdm.refine() fpoint_ises.append(cdm.createCoarsePointIS()) # Remove interior facet label (re-construct from # complement of exterior facets). Necessary because the # refinement just marks points "underneath" the refined # facet with the appropriate label. This works for # exterior, but not marked interior facets rdm.removeLabel("interior_facets") # Remove vertex (and edge) points from labels on exterior # facets. Interior facets will be relabeled in Mesh # construction below. impl.filter_exterior_facet_labels(rdm) rdm.removeLabel("op2_core") rdm.removeLabel("op2_non_core") rdm.removeLabel("op2_exec_halo") rdm.removeLabel("op2_non_exec_halo") dm_hierarchy.append(rdm) cdm = rdm # Fix up coords if refining embedded circle or sphere if hasattr(m, '_radius'): # FIXME, really we need some CAD-like representation # of the boundary we're trying to conform to. This # doesn't DTRT really for cubed sphere meshes (the # refined meshes are no longer gnonomic). coords = cdm.getCoordinatesLocal().array.reshape( -1, m.geometric_dimension()) scale = m._radius / np.linalg.norm(coords, axis=1).reshape( -1, 1) coords *= scale hierarchy = [m] + [ mesh.Mesh(dm, dim=m.ufl_cell().geometric_dimension(), distribute=False, reorder=reorder) for i, dm in enumerate(dm_hierarchy) ] for m in hierarchy: m._non_overlapped_lgmap = impl.create_lgmap(m._plex) m._non_overlapped_nent = [] for d in range(m._plex.getDimension() + 1): m._non_overlapped_nent.append(m._plex.getDepthStratum(d)) m.init() m._overlapped_lgmap = impl.create_lgmap(m._plex) # On coarse mesh n, a map of consistent cell orientations and # vertex permutations for the fine cells on each coarse cell. self._cells_vperm = [] for mc, mf, fpointis in zip(hierarchy[:-1], hierarchy[1:], fpoint_ises): mc._fpointIS = fpointis c2f = impl.coarse_to_fine_cells(mc, mf) P1c = functionspace.FunctionSpace(mc, 'CG', 1) P1f = functionspace.FunctionSpace(mf, 'CG', 1) self._cells_vperm.append(impl.compute_orientations(P1c, P1f, c2f)) self._hierarchy = tuple(hierarchy[::refinements_per_level]) self._unskipped_hierarchy = tuple(hierarchy) for level, m in enumerate(self): set_level(m, self, level) # Attach fractional levels to skipped parts # This allows us to do magic under the hood so that multigrid # on skipping hierarchies still works! for level, m in enumerate(hierarchy): if level % refinements_per_level == 0: continue set_level(m, self, Fraction(level, refinements_per_level)) self.refinements_per_level = refinements_per_level
def CubedSphereMesh(radius, refinement_level=0, degree=1, reorder=None, use_dmplex_refinement=False): """Generate an cubed approximation to the surface of the sphere. :arg radius: The radius of the sphere to approximate. :kwarg refinement_level: optional number of refinements (0 is a cube). :kwarg degree: polynomial degree of coordinate space (defaults to 1: bilinear quads) :kwarg reorder: (optional), should the mesh be reordered? :kwarg use_dmplex_refinement: (optional), use dmplex to apply the refinement. """ if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") if use_dmplex_refinement: # vertices of a cube with an edge length of 2 vertices = np.array([[-1., -1., -1.], [1., -1., -1.], [-1., 1., -1.], [1., 1., -1.], [-1., -1., 1.], [1., -1., 1.], [-1., 1., 1.], [1., 1., 1.]]) # faces of the base cube # bottom face viewed from above # 2 3 # 0 1 # top face viewed from above # 6 7 # 4 5 faces = np.array([[0, 1, 3, 2], # bottom [4, 5, 7, 6], # top [0, 1, 5, 4], [2, 3, 7, 6], [0, 2, 6, 4], [1, 3, 7, 5]], dtype=np.int32) plex = mesh._from_cell_list(2, faces, vertices) plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() # rescale points to the sphere # this is not the same as the gnonomic transformation coords = plex.getCoordinatesLocal().array.reshape(-1, 3) scale = (radius / np.linalg.norm(coords, axis=1)).reshape(-1, 1) coords *= scale else: cells, coords = _cubedsphere_cells_and_coords(radius, refinement_level) plex = mesh._from_cell_list(2, cells, coords) m = mesh.Mesh(plex, dim=3, reorder=reorder) if degree > 1: new_coords = function.Function(functionspace.VectorFunctionSpace(m, "Q", degree)) new_coords.interpolate(expression.Expression(("x[0]", "x[1]", "x[2]"))) # "push out" to sphere new_coords.dat.data[:] *= (radius / np.linalg.norm(new_coords.dat.data, axis=1)).reshape(-1, 1) m = mesh.Mesh(new_coords) return m
def UnitTetrahedronMesh(): """Generate a mesh of the reference tetrahedron""" coords = [[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]] cells = [[0, 1, 2, 3]] plex = mesh._from_cell_list(3, cells, coords) return mesh.Mesh(plex, reorder=False)
def BoxMesh(nx, ny, nz, Lx, Ly, Lz, reorder=None, comm=COMM_WORLD): """Generate a mesh of a 3D box. :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg nz: The number of cells in the z direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :arg Lz: The extent in the z direction :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on (defaults to COMM_WORLD). The boundary surfaces are numbered as follows: * 1: plane x == 0 * 2: plane x == Lx * 3: plane y == 0 * 4: plane y == Ly * 5: plane z == 0 * 6: plane z == Lz """ for n in (nx, ny, nz): if n <= 0 or n % 1: raise ValueError("Number of cells must be a postive integer") xcoords = np.linspace(0, Lx, nx + 1, dtype=np.double) ycoords = np.linspace(0, Ly, ny + 1, dtype=np.double) zcoords = np.linspace(0, Lz, nz + 1, dtype=np.double) # X moves fastest, then Y, then Z coords = np.asarray(np.meshgrid(xcoords, ycoords, zcoords)).swapaxes(0, 3).reshape(-1, 3) i, j, k = np.meshgrid(np.arange(nx, dtype=np.int32), np.arange(ny, dtype=np.int32), np.arange(nz, dtype=np.int32)) v0 = k*(nx + 1)*(ny + 1) + j*(nx + 1) + i v1 = v0 + 1 v2 = v0 + (nx + 1) v3 = v1 + (nx + 1) v4 = v0 + (nx + 1)*(ny + 1) v5 = v1 + (nx + 1)*(ny + 1) v6 = v2 + (nx + 1)*(ny + 1) v7 = v3 + (nx + 1)*(ny + 1) cells = [v0, v1, v3, v7, v0, v1, v7, v5, v0, v5, v7, v4, v0, v3, v2, v7, v0, v6, v4, v7, v0, v2, v6, v7] cells = np.asarray(cells).swapaxes(0, 3).reshape(-1, 4) plex = mesh._from_cell_list(3, cells, coords, comm) # Apply boundary IDs plex.createLabel("boundary_ids") plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() xtol = Lx/(2*nx) ytol = Ly/(2*ny) ztol = Lz/(2*nz) for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) if abs(face_coords[0]) < xtol and abs(face_coords[3]) < xtol and abs(face_coords[6]) < xtol: plex.setLabelValue("boundary_ids", face, 1) if abs(face_coords[0] - Lx) < xtol and abs(face_coords[3] - Lx) < xtol and abs(face_coords[6] - Lx) < xtol: plex.setLabelValue("boundary_ids", face, 2) if abs(face_coords[1]) < ytol and abs(face_coords[4]) < ytol and abs(face_coords[7]) < ytol: plex.setLabelValue("boundary_ids", face, 3) if abs(face_coords[1] - Ly) < ytol and abs(face_coords[4] - Ly) < ytol and abs(face_coords[7] - Ly) < ytol: plex.setLabelValue("boundary_ids", face, 4) if abs(face_coords[2]) < ztol and abs(face_coords[5]) < ztol and abs(face_coords[8]) < ztol: plex.setLabelValue("boundary_ids", face, 5) if abs(face_coords[2] - Lz) < ztol and abs(face_coords[5] - Lz) < ztol and abs(face_coords[8] - Lz) < ztol: plex.setLabelValue("boundary_ids", face, 6) return mesh.Mesh(plex, reorder=reorder)
def __init__(self, m, refinement_levels, reorder=None): """Build a hierarchy of meshes by uniformly refining a coarse mesh. :arg m: the coarse :func:`~.Mesh` to refine :arg refinement_levels: the number of levels of refinement :arg reorder: optional flag indicating whether to reorder the refined meshes. """ if m.ufl_cell().cellname() not in ["triangle", "interval"]: raise NotImplementedError( "Only supported on intervals and triangles") m._plex.setRefinementUniform(True) dm_hierarchy = [] cdm = m._plex fpoint_ises = [] if MPI.comm.size > 1 and m._grown_halos: raise RuntimeError( "Cannot refine parallel overlapped meshes (make sure the MeshHierarchy is built immediately after the Mesh)" ) for i in range(refinement_levels): rdm = cdm.refine() fpoint_ises.append(cdm.createCoarsePointIS()) # Remove interior facet label (re-construct from # complement of exterior facets). Necessary because the # refinement just marks points "underneath" the refined # facet with the appropriate label. This works for # exterior, but not marked interior facets rdm.removeLabel("interior_facets") # Remove vertex (and edge) points from labels on exterior # facets. Interior facets will be relabeled in Mesh # construction below. impl.filter_exterior_facet_labels(rdm) rdm.removeLabel("op2_core") rdm.removeLabel("op2_non_core") rdm.removeLabel("op2_exec_halo") rdm.removeLabel("op2_non_exec_halo") dm_hierarchy.append(rdm) cdm = rdm # Fix up coords if refining embedded circle or sphere if hasattr(m, '_circle_manifold'): coords = cdm.getCoordinatesLocal().array.reshape(-1, 2) scale = m._circle_manifold / np.linalg.norm( coords, axis=1).reshape(-1, 1) coords *= scale elif hasattr(m, '_icosahedral_sphere'): coords = cdm.getCoordinatesLocal().array.reshape(-1, 3) scale = m._icosahedral_sphere / np.linalg.norm( coords, axis=1).reshape(-1, 1) coords *= scale hierarchy = [m] + [ mesh.Mesh(dm, dim=m.ufl_cell().geometric_dimension(), distribute=False, reorder=reorder) for i, dm in enumerate(dm_hierarchy) ] self._hierarchy = tuple( [set_level(o, self, lvl) for lvl, o in enumerate(hierarchy)]) for m in self: m._non_overlapped_lgmap = impl.create_lgmap(m._plex) m._non_overlapped_nent = [] for d in range(m._plex.getDimension() + 1): m._non_overlapped_nent.append(m._plex.getDepthStratum(d)) m.init() m._overlapped_lgmap = impl.create_lgmap(m._plex) # On coarse mesh n, a map of consistent cell orientations and # vertex permutations for the fine cells on each coarse cell. self._cells_vperm = [] for mc, mf, fpointis in zip(self._hierarchy[:-1], self._hierarchy[1:], fpoint_ises): mc._fpointIS = fpointis c2f = impl.coarse_to_fine_cells(mc, mf) P1c = functionspace.FunctionSpace(mc, 'CG', 1) P1f = functionspace.FunctionSpace(mf, 'CG', 1) self._cells_vperm.append(impl.compute_orientations(P1c, P1f, c2f))
def UnitTriangleMesh(): """Generate a mesh of the reference triangle""" coords = [[0., 0.], [1., 0.], [0., 1.]] cells = [[0, 1, 2]] plex = mesh._from_cell_list(2, cells, coords) return mesh.Mesh(plex, reorder=False)
def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): r""" Create a firedrake mesh corresponding to one :class:`~meshmode.mesh.Mesh`'s :class:`~meshmode.mesh.SimplexElementGroup`. :param mesh: A :class:`~meshmode.mesh.Mesh` to convert with at least one :class:`~meshmode.mesh.SimplexElementGroup`. 'mesh.is_conforming' must evaluate to *True*. 'mesh' must have vertices supplied, i.e. 'mesh.vertices' must not be *None*. :param group_nr: The group number to be converted into a firedrake mesh. The corresponding group must be of type :class:`~meshmode.mesh.SimplexElementGroup`. If *None* and *mesh* has only one group, that group is used. Otherwise, a *ValueError* is raised. :param comm: The communicator to build the dmplex mesh on :return: A tuple *(fdrake_mesh, fdrake_cell_ordering, perm2cell)* where * *fdrake_mesh* is a :mod:`firedrake` `firedrake.mesh.MeshGeometry` corresponding to *mesh* * *fdrake_cell_ordering* is a numpy array whose *i*\ th element in *mesh* (i.e. the *i*\ th element in *mesh.groups[group_nr].vertex_indices*) corresponds to the *fdrake_cell_ordering[i]*\ th :mod:`firedrake` cell * *perm2cell* is a dictionary, mapping tuples to 1-D numpy arrays of meshmode element indices. Each meshmode element index appears in exactly one of these arrays. The corresponding tuple describes how firedrake reordered the local vertex indices on that cell. In particular, if *c* is in the list *perm2cell[p]* for a tuple *p*, then the *p[i]*\ th local vertex of the *fdrake_cell_ordering[c]*\ th firedrake cell corresponds to the *i*\ th local vertex of the *c*\ th meshmode element. .. warning:: Currently, no custom boundary tags are exported along with the mesh. :mod:`firedrake` seems to only allow one marker on each facet, whereas :mod:`meshmode` allows many. """ if not isinstance(mesh, Mesh): raise TypeError("'mesh' must of type meshmode.mesh.Mesh," " not '%s'." % type(mesh)) if group_nr is None: if len(mesh.groups) != 1: raise ValueError("'group_nr' is *None* but 'mesh' has " "more than one group.") group_nr = 0 if not isinstance(group_nr, int): raise TypeError("Expecting 'group_nr' to be of type int, not " f"'{type(group_nr)}'") if group_nr < 0 or group_nr >= len(mesh.groups): raise ValueError("'group_nr' is an invalid group index:" f" '{group_nr}' fails to satisfy " f"0 <= {group_nr} < {len(mesh.groups)}") if not isinstance(mesh.groups[group_nr], SimplexElementGroup): raise TypeError("Expecting 'mesh.groups[group_nr]' to be of type " "meshmode.mesh.SimplexElementGroup, not " f"'{type(mesh.groups[group_nr])}'") if mesh.vertices is None: raise ValueError("'mesh' has no vertices " "('mesh.vertices' is *None*)") if not mesh.is_conforming: raise ValueError(f"'mesh.is_conforming' is {mesh.is_conforming} " "instead of *True*. Converting non-conforming " " meshes to Firedrake is not supported") # Get the vertices and vertex indices of the requested group with ProcessLogger(logger, "Obtaining vertices from selected group"): group = mesh.groups[group_nr] fd2mm_indices = np.unique(group.vertex_indices.flatten()) coords = mesh.vertices[:, fd2mm_indices].T mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) # Get a dmplex object and then a mesh topology with ProcessLogger(logger, "Building dmplex object and MeshTopology"): if comm is None: from pyop2.mpi import COMM_WORLD comm = COMM_WORLD # FIXME : not sure how to get around the private accesses import firedrake.mesh as fd_mesh plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) # Nb : One might be tempted to pass reorder=False and thereby save some # hassle in exchange for forcing firedrake to have slightly # less efficient caching. Unfortunately, that only prevents # the cells from being reordered, and does not prevent the # vertices from being (locally) reordered on each cell... # the tl;dr is we don't actually save any hassle top = fd_mesh.Mesh(plex, dim=mesh.ambient_dim) # mesh topology top.init() # Get new element ordering: with ProcessLogger(logger, "Determining permutations applied" " to local vertex numbers"): c_start, c_end = top._topology_dm.getHeightStratum(0) cell_index_mm2fd = np.vectorize(top._cell_numbering.getOffset)( np.arange(c_start, c_end)) v_start, v_end = top._topology_dm.getDepthStratum(0) # Firedrake goes crazy reordering local vertex numbers, # we've got to work to figure out what changes they made. # # *perm2cells* will map permutations of local vertex numbers to # the list of all the meshmode cells # which firedrake reordered according to that permutation # # Permutations on *n* vertices are stored as a tuple # containing all of the integers *0*, *1*, *2*, ..., *n-1* # exactly once. A permutation *p* # represents relabeling the *i*\ th local vertex # of a meshmode element as the *p[i]*\ th local vertex # in the corresponding firedrake cell. # # *perm2cells[p]* is a list of all the meshmode element indices # for which *p* represents the reordering applied by firedrake perm2cells = {} for mm_cell_id, dmp_ids in enumerate(top.cell_closure[cell_index_mm2fd]): # look at order of vertices in firedrake cell vert_dmp_ids = \ dmp_ids[np.logical_and(v_start <= dmp_ids, dmp_ids < v_end)] fdrake_order = vert_dmp_ids - v_start # get original order mm_order = mesh.groups[group_nr].vertex_indices[mm_cell_id] # want permutation p so that mm_order[p] = fdrake_order # To do so, look at permutations acting by composition. # # mm_order \circ argsort(mm_order) = # fdrake_order \circ argsort(fdrake_order) # so # mm_order \circ argsort(mm_order) \circ inv(argsort(fdrake_order)) # = fdrake_order # # argsort acts as an inverse, so the desired permutation is: perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) perm2cells.setdefault(perm, []) perm2cells[perm].append(mm_cell_id) # Make perm2cells map to numpy arrays instead of lists perm2cells = {perm: np.array(cells) for perm, cells in perm2cells.items()} # Now make a coordinates function with ProcessLogger(logger, "Building firedrake function " "space for mesh coordinates"): from firedrake import VectorFunctionSpace, Function coords_fspace = VectorFunctionSpace(top, "CG", group.order, dim=mesh.ambient_dim) coords = Function(coords_fspace) # get firedrake unit nodes and map onto meshmode reference element fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) fd_unit_nodes = get_finat_element_unit_nodes(coords_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) basis = simplex_best_available_basis(group.dim, group.order) resampling_mat = resampling_matrix(basis, new_nodes=fd_unit_nodes, old_nodes=group.unit_nodes) # Store the meshmode data resampled to firedrake unit nodes # (but still in meshmode order) resampled_group_nodes = np.matmul(group.nodes, resampling_mat.T) # Now put the nodes in the right local order # nodes is shaped *(ambient dim, nelements, nunit nodes)* with ProcessLogger(logger, "Storing meshmode mesh coordinates" " in firedrake nodal order"): from meshmode.mesh.processing import get_simplex_element_flip_matrix for perm, cells in perm2cells.items(): flip_mat = get_simplex_element_flip_matrix(group.order, fd_unit_nodes, perm) flip_mat = np.rint(flip_mat).astype(np.int32) resampled_group_nodes[:, cells, :] = \ np.matmul(resampled_group_nodes[:, cells, :], flip_mat.T) # store resampled data in right cell ordering with ProcessLogger(logger, "resampling mesh coordinates to " "firedrake unit nodes"): reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] coords.dat.data[reordered_cell_node_list, :] = \ resampled_group_nodes.transpose((1, 2, 0)) return fd_mesh.Mesh(coords), cell_index_mm2fd, perm2cells
def PeriodicRectangleMesh(nx, ny, Lx, Ly, quadrilateral=False, reorder=None): """Generate a periodic rectangular mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg Lx: The extent in the x direction :arg Ly: The extent in the y direction :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered """ if nx < 3 or ny < 3: raise ValueError("2D periodic meshes with fewer than 3 \ cells in each direction are not currently supported") m = TorusMesh(nx, ny, 1.0, 0.5, quadrilateral=quadrilateral, reorder=reorder) coord_fs = VectorFunctionSpace(m, 'DG', 1, dim=2) old_coordinates = m.coordinates new_coordinates = Function(coord_fs) periodic_kernel = """ double pi = 3.141592653589793; double eps = 1e-12; double bigeps = 1e-1; double phi, theta, Y, Z; Y = 0.0; Z = 0.0; for(int i=0; i<old_coords.dofs; i++) { Y += old_coords[i][1]; Z += old_coords[i][2]; } for(int i=0; i<new_coords.dofs; i++) { phi = atan2(old_coords[i][1], old_coords[i][0]); if (fabs(sin(phi)) > bigeps) theta = atan2(old_coords[i][2], old_coords[i][1]/sin(phi) - 1.0); else theta = atan2(old_coords[i][2], old_coords[i][0]/cos(phi) - 1.0); new_coords[i][0] = phi/(2.0*pi); if(new_coords[i][0] < -eps) { new_coords[i][0] += 1.0; } if(fabs(new_coords[i][0]) < eps && Y < 0.0) { new_coords[i][0] = 1.0; } new_coords[i][1] = theta/(2.0*pi); if(new_coords[i][1] < -eps) { new_coords[i][1] += 1.0; } if(fabs(new_coords[i][1]) < eps && Z < 0.0) { new_coords[i][1] = 1.0; } new_coords[i][0] *= Lx[0]; new_coords[i][1] *= Ly[0]; } """ cLx = Constant(Lx) cLy = Constant(Ly) par_loop( periodic_kernel, dx, { "new_coords": (new_coordinates, WRITE), "old_coords": (old_coordinates, READ), "Lx": (cLx, READ), "Ly": (cLy, READ) }) return mesh.Mesh(new_coordinates)