Source code for firedrake.utility_meshes

import numpy as np

import ufl

from pyop2.mpi import COMM_WORLD
from firedrake.utils import IntType, ScalarType

from firedrake import (
    VectorFunctionSpace,
    FunctionSpace,
    Function,
    Constant,
    assemble,
    Interpolate,
    FiniteElement,
    interval,
    tetrahedron,
    atan2,
    pi,
    as_vector,
    SpatialCoordinate,
    conditional,
    gt,
    as_tensor,
    dot,
    And,
    Or,
    sin,
    cos,
    real
)
from firedrake.cython import dmcommon
from firedrake import mesh
from firedrake import function
from firedrake import functionspace
from firedrake.petsc import PETSc

from pyadjoint.tape import no_annotations


__all__ = [
    "IntervalMesh",
    "UnitIntervalMesh",
    "PeriodicIntervalMesh",
    "PeriodicUnitIntervalMesh",
    "UnitTriangleMesh",
    "RectangleMesh",
    "TensorRectangleMesh",
    "SquareMesh",
    "UnitSquareMesh",
    "PeriodicRectangleMesh",
    "PeriodicSquareMesh",
    "PeriodicUnitSquareMesh",
    "CircleManifoldMesh",
    "UnitDiskMesh",
    "UnitBallMesh",
    "UnitTetrahedronMesh",
    "TensorBoxMesh",
    "BoxMesh",
    "CubeMesh",
    "UnitCubeMesh",
    "PeriodicBoxMesh",
    "PeriodicUnitCubeMesh",
    "IcosahedralSphereMesh",
    "UnitIcosahedralSphereMesh",
    "OctahedralSphereMesh",
    "UnitOctahedralSphereMesh",
    "CubedSphereMesh",
    "UnitCubedSphereMesh",
    "TorusMesh",
    "AnnulusMesh",
    "SolidTorusMesh",
    "CylinderMesh",
]


distribution_parameters_no_overlap = {"partition": True,
                                      "overlap_type": (mesh.DistributedMeshOverlapType.NONE, 0)}
reorder_noop = False


def _postprocess_periodic_mesh(coords, comm, distribution_parameters, reorder, name, distribution_name, permutation_name):
    dm = coords.function_space().mesh().topology.topology_dm
    dm.removeLabel("pyop2_core")
    dm.removeLabel("pyop2_owned")
    dm.removeLabel("pyop2_ghost")
    dm.removeLabel("exterior_facets")
    dm.removeLabel("interior_facets")
    V = coords.function_space()
    dmcommon._set_dg_coordinates(dm,
                                 V.finat_element,
                                 V.dm.getLocalSection(),
                                 coords.dat._vec)
    return mesh.Mesh(
        dm,
        comm=comm,
        distribution_parameters=distribution_parameters,
        reorder=reorder,
        name=name,
        distribution_name=distribution_name,
        permutation_name=permutation_name,
    )


[docs] @PETSc.Log.EventDecorator() def IntervalMesh( ncells, length_or_left, right=None, distribution_parameters=None, reorder=False, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """ 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 distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. 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.plex_from_cell_list( 1, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) # Apply boundary IDs plex.createLabel(dmcommon.FACE_SETS_LABEL) 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(dmcommon.FACE_SETS_LABEL, v, 1) if vcoord[0] == coords[-1]: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, v, 2) m = mesh.Mesh( plex, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] @PETSc.Log.EventDecorator() def UnitIntervalMesh( ncells, distribution_parameters=None, reorder=False, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """ Generate a uniform mesh of the interval [0,1]. :arg ncells: The number of the cells over the interval. :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. The left hand (:math:`x=0`) boundary point has boundary marker 1, while the right hand (:math:`x=1`) point has marker 2. """ return IntervalMesh( ncells, length_or_left=1.0, distribution_parameters=distribution_parameters, reorder=reorder, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def PeriodicIntervalMesh( ncells, length, distribution_parameters=None, reorder=False, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a periodic mesh of an interval. :arg ncells: The number of cells over the interval. :arg length: The length the interval. :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ if ncells < 3: raise ValueError( "1D periodic meshes with fewer than 3 \ cells are not currently supported" ) m = CircleManifoldMesh( ncells, distribution_parameters=distribution_parameters_no_overlap, reorder=reorder_noop, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, ) indicator = Function(FunctionSpace(m, "DG", 0)) coord_fs = VectorFunctionSpace( m, FiniteElement("DG", interval, 1, variant="equispaced"), dim=1 ) new_coordinates = Function( coord_fs, name=mesh._generate_default_mesh_coordinates_name(name) ) x, y = SpatialCoordinate(m) eps = 1.e-14 indicator.interpolate(conditional(gt(real(y), 0), 0., 1.)) new_coordinates.interpolate( as_vector((conditional( gt(real(x), real(1. - eps)), indicator, # Periodic break. # Unwrap rest of circle. atan2(real(-y), real(-x))/(2 * pi) + 0.5 ) * length,)) ) return _postprocess_periodic_mesh(new_coordinates, comm, distribution_parameters, reorder, name, distribution_name, permutation_name)
[docs] @PETSc.Log.EventDecorator() def PeriodicUnitIntervalMesh( ncells, distribution_parameters=None, reorder=False, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a periodic mesh of the unit interval. :arg ncells: The number of cells in the interval. :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ return PeriodicIntervalMesh( ncells, length=1.0, distribution_parameters=distribution_parameters, reorder=reorder, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
@PETSc.Log.EventDecorator() def OneElementThickMesh( ncells, Lx, Ly, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """ 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 distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ 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.0 * X coords = np.array([X, Y]).T # a line of coordinates, with a looped topology plex = mesh.plex_from_cell_list( 2, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) mesh1 = mesh.Mesh(plex, distribution_parameters=distribution_parameters, comm=comm) 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, _ = plex.getTransitiveClosure(e) # get the row for this cell row = cell_numbering.getOffset(e) # run some checks assert closure[0] == e assert len(closure) == 6, closure edge_range = plex.getHeightStratum(1) assert all(closure[1:4] >= edge_range[0]) assert all(closure[1:4] < edge_range[1]) vertex_range = plex.getHeightStratum(2) assert all(closure[4:] >= vertex_range[0]) assert all(closure[4:] < vertex_range[1]) # enter the cell number cell_closure[row][8] = e # Get a list of unique edges edge_set = list(closure[1:4]) # there are two vertices in the cell cell_vertices = closure[4:] cell_X = np.array([0.0, 0.0], dtype=ScalarType) for i, v in enumerate(cell_vertices): cell_X[i] = coords[coords_sec.getOffset(v)] # Add in the edges for i in range(3): edge_vertex, edge_vertex_ = plex.getCone(edge_set[i]) if edge_vertex_ != edge_vertex: # we have a y-periodic edge cell_closure[row][6] = edge_set[i] cell_closure[row][7] = edge_set[i] else: # 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 # 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[4:] 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() fe_dg = FiniteElement("DQ", mesh1.ufl_cell(), 1, variant="equispaced") Vc = VectorFunctionSpace(mesh1, fe_dg) fc = Function( Vc, name=mesh._generate_default_mesh_coordinates_name(name) ).interpolate(mesh1.coordinates) mash = mesh.Mesh( fc, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) 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 lfd = local_facet_dat.data for i in range(lfd.shape[0]): if all(lfd[i, :] == np.array([3, 3])): lfd[i, :] = [2, 3] return mash
[docs] @PETSc.Log.EventDecorator() def UnitTriangleMesh( refinement_level=0, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of the reference triangle :kwarg refinement_level: Number of uniform refinements to perform :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ coords = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]] cells = [[0, 1, 2]] plex = mesh.plex_from_cell_list(2, cells, coords, comm) # mark boundary facets plex.createLabel(dmcommon.FACE_SETS_LABEL) plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() tol = 1e-2 # 0.5 would suffice for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) # |x+y-1| < eps if abs(face_coords[0] + face_coords[1] - 1) < tol and abs(face_coords[2] + face_coords[3] - 1) < tol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 1) # |x| < eps if abs(face_coords[0]) < tol and abs(face_coords[2]) < tol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 2) # |y| < eps if abs(face_coords[1]) < tol and abs(face_coords[3]) < tol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 3) plex.removeLabel("boundary_faces") plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() plex.setName(mesh._generate_default_mesh_topology_name(name)) return mesh.Mesh( plex, reorder=False, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, )
[docs] @PETSc.Log.EventDecorator() def RectangleMesh( nx, ny, Lx, Ly, originX=0., originY=0., quadrilateral=False, reorder=None, diagonal="left", distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=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 X coordinates of the upper right corner of the rectangle. :arg Ly: The Y coordinates of the upper right corner of the rectangle. :arg originX: The X coordinates of the lower left corner of the rectangle. :arg originY: The Y coordinates of the lower left corner of the rectangle. :kwarg quadrilateral: (optional), creates quadrilateral mesh, defaults to False :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg diagonal: For triangular meshes, should the diagonal got from bottom left to top right (``"right"``), or top left to bottom right (``"left"``), or put in both diagonals (``"crossed"``). :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. The boundary edges in this mesh are numbered as follows: * 1: plane x == originX * 2: plane x == Lx * 3: plane y == originY * 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(originX, Lx, nx + 1, dtype=np.double) ycoords = np.linspace(originY, Ly, ny + 1, dtype=np.double) return TensorRectangleMesh( xcoords, ycoords, quadrilateral=quadrilateral, reorder=reorder, diagonal=diagonal, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] def TensorRectangleMesh( xcoords, ycoords, quadrilateral=False, reorder=None, diagonal="left", distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a rectangular mesh :arg xcoords: mesh points for the x direction :arg ycoords: mesh points for the y direction :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg diagonal: For triangular meshes, should the diagonal got from bottom left to top right (``"right"``), or top left to bottom right (``"left"``), or put in both diagonals (``"crossed"``). The boundary edges in this mesh are numbered as follows: * 1: plane x == xcoords[0] * 2: plane x == xcoords[-1] * 3: plane y == ycoords[0] * 4: plane y == ycoords[-1] """ xcoords = np.unique(xcoords) ycoords = np.unique(ycoords) nx = np.size(xcoords) - 1 ny = np.size(ycoords) - 1 for n in (nx, ny): if n <= 0: raise ValueError("Number of cells must be a postive integer") 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)) if not quadrilateral and diagonal == "crossed": xs = 0.5 * (xcoords[1:] + xcoords[:-1]) ys = 0.5 * (ycoords[1:] + ycoords[:-1]) extra = np.asarray(np.meshgrid(xs, ys)).swapaxes(0, 2).reshape(-1, 2) coords = np.vstack([coords, extra]) # # 2-----3 # | \ / | # | 4 | # | / \ | # 0-----1 cells = [ i * (ny + 1) + j, i * (ny + 1) + j + 1, (i + 1) * (ny + 1) + j, (i + 1) * (ny + 1) + j + 1, (nx + 1) * (ny + 1) + i * ny + j, ] cells = np.asarray(cells).swapaxes(0, 2).reshape(-1, 5) idx = [0, 1, 4, 0, 2, 4, 2, 3, 4, 3, 1, 4] cells = cells[:, idx].reshape(-1, 3) else: 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.plex_from_cell_list( 2, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) # mark boundary facets plex.createLabel(dmcommon.FACE_SETS_LABEL) 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 = 0.5 * min(xcoords[1] - xcoords[0], xcoords[-1] - xcoords[-2]) ytol = 0.5 * min(ycoords[1] - ycoords[0], ycoords[-1] - ycoords[-2]) x0 = xcoords[0] x1 = xcoords[-1] y0 = ycoords[0] y1 = ycoords[-1] for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) if abs(face_coords[0] - x0) < xtol and abs(face_coords[2] - x0) < xtol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 1) if abs(face_coords[0] - x1) < xtol and abs(face_coords[2] - x1) < xtol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 2) if abs(face_coords[1] - y0) < ytol and abs(face_coords[3] - y0) < ytol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 3) if abs(face_coords[1] - y1) < ytol and abs(face_coords[3] - y1) < ytol: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 4) plex.removeLabel("boundary_faces") m = mesh.Mesh( plex, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] @PETSc.Log.EventDecorator() def SquareMesh( nx, ny, L, reorder=None, quadrilateral=False, diagonal="left", distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a square mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg L: The extent in the x and y directions :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. The boundary edges in this mesh are numbered as follows: * 1: plane x == 0 * 2: plane x == L * 3: plane y == 0 * 4: plane y == L """ return RectangleMesh( nx, ny, L, L, reorder=reorder, quadrilateral=quadrilateral, diagonal=diagonal, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def UnitSquareMesh( nx, ny, reorder=None, diagonal="left", quadrilateral=False, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a unit square mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. The boundary edges in this mesh are numbered as follows: * 1: plane x == 0 * 2: plane x == 1 * 3: plane y == 0 * 4: plane y == 1 """ return SquareMesh( nx, ny, 1, reorder=reorder, quadrilateral=quadrilateral, diagonal=diagonal, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def PeriodicRectangleMesh( nx, ny, Lx, Ly, direction="both", quadrilateral=False, reorder=None, distribution_parameters=None, diagonal=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=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 :arg direction: The direction of the periodicity, one of ``"both"``, ``"x"`` or ``"y"``. :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: (optional), one of ``"crossed"``, ``"left"``, ``"right"``. Not valid for quad meshes. Only used for direction ``"x"`` or direction ``"y"``. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. 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, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) 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, distribution_parameters=distribution_parameters, diagonal=diagonal, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, ) 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_noop, distribution_parameters=distribution_parameters_no_overlap, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, ) coord_family = "DQ" if quadrilateral else "DG" cell = "quadrilateral" if quadrilateral else "triangle" coord_fs = VectorFunctionSpace( m, FiniteElement(coord_family, cell, 1, variant="equispaced"), dim=2 ) new_coordinates = Function( coord_fs, name=mesh._generate_default_mesh_coordinates_name(name) ) x, y, z = SpatialCoordinate(m) eps = 1.e-14 indicator_y = Function(FunctionSpace(m, coord_family, 0)) indicator_y.interpolate(conditional(gt(real(y), 0), 0., 1.)) x_coord = Function(FunctionSpace(m, coord_family, 1, variant="equispaced")) x_coord.interpolate( # Periodic break. conditional(And(gt(real(eps), real(abs(y))), gt(real(x), 0.)), indicator_y, # Unwrap rest of circle. atan2(real(-y), real(-x))/(2*pi)+0.5) ) phi_coord = as_vector([cos(2*pi*x_coord), sin(2*pi*x_coord)]) dr = dot(as_vector((x, y))-phi_coord, phi_coord) indicator_z = Function(FunctionSpace(m, coord_family, 0)) indicator_z.interpolate(conditional(gt(real(z), 0), 0., 1.)) new_coordinates.interpolate(as_vector(( x_coord * Lx, # Periodic break. conditional(And(gt(real(eps), real(abs(z))), gt(real(dr), 0.)), indicator_z, # Unwrap rest of circle. atan2(real(-z), real(-dr))/(2*pi)+0.5) * Ly ))) return _postprocess_periodic_mesh(new_coordinates, comm, distribution_parameters, reorder, name, distribution_name, permutation_name)
[docs] @PETSc.Log.EventDecorator() def PeriodicSquareMesh( nx, ny, L, direction="both", quadrilateral=False, reorder=None, distribution_parameters=None, diagonal=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a periodic square mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg L: The extent in the x and y directions :arg direction: The direction of the periodicity, one of ``"both"``, ``"x"`` or ``"y"``. :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: (optional), one of ``"crossed"``, ``"left"``, ``"right"``. Not valid for quad meshes. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. If direction == "x" the boundary edges in this mesh are numbered as follows: * 1: plane y == 0 * 2: plane y == L If direction == "y" the boundary edges are: * 1: plane x == 0 * 2: plane x == L """ return PeriodicRectangleMesh( nx, ny, L, L, direction=direction, quadrilateral=quadrilateral, reorder=reorder, distribution_parameters=distribution_parameters, diagonal=diagonal, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def PeriodicUnitSquareMesh( nx, ny, direction="both", reorder=None, quadrilateral=False, distribution_parameters=None, diagonal=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a periodic unit square mesh :arg nx: The number of cells in the x direction :arg ny: The number of cells in the y direction :arg direction: The direction of the periodicity, one of ``"both"``, ``"x"`` or ``"y"``. :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: (optional), one of ``"crossed"``, ``"left"``, ``"right"``. Not valid for quad meshes. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. If direction == "x" the boundary edges in this mesh are numbered as follows: * 1: plane y == 0 * 2: plane y == 1 If direction == "y" the boundary edges are: * 1: plane x == 0 * 2: plane x == 1 """ return PeriodicSquareMesh( nx, ny, 1.0, direction=direction, reorder=reorder, quadrilateral=quadrilateral, distribution_parameters=distribution_parameters, diagonal=diagonal, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def CircleManifoldMesh( ncells, radius=1, degree=1, distribution_parameters=None, reorder=False, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """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. :kwarg degree: polynomial degree of coordinate space (e.g., cells are straight line segments if degree=1). :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ if ncells < 3: raise ValueError("CircleManifoldMesh must have at least three cells") vertices = radius * np.column_stack( ( np.cos(np.arange(ncells, dtype=np.double) * (2 * np.pi / ncells)), np.sin(np.arange(ncells, dtype=np.double) * (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.plex_from_cell_list( 1, cells, vertices, comm, mesh._generate_default_mesh_topology_name(name) ) m = mesh.Mesh( plex, dim=2, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) if degree > 1: new_coords = function.Function( functionspace.VectorFunctionSpace(m, "CG", degree) ) new_coords.interpolate(ufl.SpatialCoordinate(m)) # "push out" to circle new_coords.dat.data[:] *= ( radius / np.linalg.norm(new_coords.dat.data, axis=1) ).reshape(-1, 1) m = mesh.Mesh( new_coords, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) m._radius = radius return m
[docs] @PETSc.Log.EventDecorator() def UnitDiskMesh( refinement_level=0, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of the unit disk in 2D :kwarg refinement_level: optional number of refinements (0 is a diamond) :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ vertices = np.array( [[0, 0], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]], dtype=np.double, ) cells = np.array( [ [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 6], [0, 6, 7], [0, 7, 8], [0, 8, 1], ], np.int32, ) plex = mesh.plex_from_cell_list( 2, cells, vertices, comm, mesh._generate_default_mesh_topology_name(name) ) # mark boundary facets plex.createLabel(dmcommon.FACE_SETS_LABEL) plex.markBoundaryFaces("boundary_faces") if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() for face in boundary_faces: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 1) plex.removeLabel("boundary_faces") plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() coords = plex.getCoordinatesLocal().array.reshape(-1, 2) for x in coords: norm = np.sqrt(np.dot(x, x)) if norm > 1.0 / (1 << (refinement_level + 1)): t = np.max(np.abs(x)) / norm x[:] *= t m = mesh.Mesh( plex, dim=2, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] @PETSc.Log.EventDecorator() def UnitBallMesh( refinement_level=0, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of the unit ball in 3D :kwarg refinement_level: optional number of refinements (0 is an octahedron) :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional MPI communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ vertices = np.array( [ [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, 0, 0], [0, -1, 0], [0, 0, -1], ], dtype=np.double, ) cells = np.array( [ [0, 1, 2, 3], [0, 2, 4, 3], [0, 4, 5, 3], [0, 5, 1, 3], [0, 2, 1, 6], [0, 4, 2, 6], [0, 5, 4, 6], [0, 1, 5, 6], ], np.int32, ) plex = mesh.plex_from_cell_list( 3, cells, vertices, comm, mesh._generate_default_mesh_topology_name(name) ) plex.createLabel(dmcommon.FACE_SETS_LABEL) plex.markBoundaryFaces("boundary_faces") if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() for face in boundary_faces: plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 1) plex.removeLabel("boundary_faces") plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() coords = plex.getCoordinatesLocal().array.reshape(-1, 3) for x in coords: norm = np.sqrt(np.dot(x, x)) if norm > 1.0 / (1 << (refinement_level + 1)): t = np.sum(np.abs(x)) / norm x[:] *= t m = mesh.Mesh( plex, dim=3, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] @PETSc.Log.EventDecorator() def UnitTetrahedronMesh( comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of the reference tetrahedron. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ coords = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] cells = [[0, 1, 2, 3]] plex = mesh.plex_from_cell_list( 3, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) m = mesh.Mesh( plex, reorder=False, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] def TensorBoxMesh( xcoords, ycoords, zcoords, reorder=None, distribution_parameters=None, diagonal="default", comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of a 3D box. :arg xcoords: Location of nodes in the x direction :arg ycoords: Location of nodes in the y direction :arg zcoords: Location of nodes in the z direction :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: Two ways of cutting hexadra, should be cut into 6 tetrahedra (``"default"``), or 5 tetrahedra thus less biased (``"crossed"``) :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. The boundary surfaces are numbered as follows: * 1: plane x == xcoords[0] * 2: plane x == xcoords[-1] * 3: plane y == ycoords[0] * 4: plane y == ycoords[-1] * 5: plane z == zcoords[0] * 6: plane z == zcoords[-1] """ xcoords = np.unique(xcoords) ycoords = np.unique(ycoords) zcoords = np.unique(zcoords) nx = np.size(xcoords)-1 ny = np.size(ycoords)-1 nz = np.size(zcoords)-1 for n in (nx, ny, nz): if n <= 0 or n % 1: raise ValueError("Number of cells must be a postive integer") # 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), ) if diagonal == "default": 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).reshape(-1, ny, nx, nz).swapaxes(0, 3).reshape(-1, 4) elif diagonal == "crossed": 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) # There are only five tetrahedra in this cutting of hexahedra cells = [ [v0, v1, v2, v4], [v1, v7, v5, v4], [v1, v2, v3, v7], [v2, v4, v6, v7], [v1, v2, v7, v4], ] cells = np.asarray(cells).reshape(-1, ny, nx, nz).swapaxes(0, 3).reshape(-1, 4) raise NotImplementedError( "The crossed cutting of hexahedra has a broken connectivity issue for Pk (k>1) elements" ) else: raise ValueError("Unrecognised value for diagonal '%r'", diagonal) plex = mesh.plex_from_cell_list( 3, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) nvert = 3 # num. vertices on facet # Apply boundary IDs plex.createLabel(dmcommon.FACE_SETS_LABEL) plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() cdim = plex.getCoordinateDim() assert cdim == 3 if plex.getStratumSize("boundary_faces", 1) > 0: boundary_faces = plex.getStratumIS("boundary_faces", 1).getIndices() xtol = 0.5 * min(xcoords[1]-xcoords[0], xcoords[-1] - xcoords[-2]) ytol = 0.5 * min(ycoords[1]-ycoords[0], ycoords[-1] - ycoords[-2]) ztol = 0.5 * min(zcoords[1]-zcoords[0], zcoords[-1] - zcoords[-2]) x0 = xcoords[0] x1 = xcoords[-1] y0 = ycoords[0] y1 = ycoords[-1] z0 = zcoords[0] z1 = zcoords[-1] for face in boundary_faces: face_coords = plex.vecGetClosure(coord_sec, coords, face) if all([abs(face_coords[0 + cdim * i] - x0) < xtol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 1) if all([abs(face_coords[0 + cdim * i] - x1) < xtol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 2) if all([abs(face_coords[1 + cdim * i] - y0) < ytol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 3) if all([abs(face_coords[1 + cdim * i] - y1) < ytol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 4) if all([abs(face_coords[2 + cdim * i] - z0) < ztol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 5) if all([abs(face_coords[2 + cdim * i] - z1) < ztol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 6) plex.removeLabel("boundary_faces") m = mesh.Mesh( plex, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] @PETSc.Log.EventDecorator() def BoxMesh( nx, ny, nz, Lx, Ly, Lz, hexahedral=False, reorder=None, distribution_parameters=None, diagonal="default", comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=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 hexahedral: (optional), creates hexahedral mesh. :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: Two ways of cutting hexadra, should be cut into 6 tetrahedra (``"default"``), or 5 tetrahedra thus less biased (``"crossed"``) :kwarg reorder: (optional), should the mesh be reordered? :kwarg comm: Optional communicator to build the mesh on. 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") if hexahedral: plex = PETSc.DMPlex().createBoxMesh((nx, ny, nz), lower=(0., 0., 0.), upper=(Lx, Ly, Lz), simplex=False, periodic=False, interpolate=True, comm=comm) plex.removeLabel(dmcommon.FACE_SETS_LABEL) nvert = 4 # num. vertices on faect # Apply boundary IDs plex.createLabel(dmcommon.FACE_SETS_LABEL) plex.markBoundaryFaces("boundary_faces") coords = plex.getCoordinates() coord_sec = plex.getCoordinateSection() cdim = plex.getCoordinateDim() assert cdim == 3 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 all([abs(face_coords[0 + cdim * i]) < xtol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 1) if all([abs(face_coords[0 + cdim * i] - Lx) < xtol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 2) if all([abs(face_coords[1 + cdim * i]) < ytol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 3) if all([abs(face_coords[1 + cdim * i] - Ly) < ytol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 4) if all([abs(face_coords[2 + cdim * i]) < ztol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 5) if all([abs(face_coords[2 + cdim * i] - Lz) < ztol for i in range(nvert)]): plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 6) plex.removeLabel("boundary_faces") m = mesh.Mesh( plex, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m else: 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) return TensorBoxMesh( xcoords, ycoords, zcoords, reorder=reorder, distribution_parameters=distribution_parameters, diagonal=diagonal, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def CubeMesh( nx, ny, nz, L, hexahedral=False, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of a cube :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 L: The extent in the x, y and z directions :kwarg hexahedral: (optional), creates hexahedral mesh. :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. The boundary surfaces are numbered as follows: * 1: plane x == 0 * 2: plane x == L * 3: plane y == 0 * 4: plane y == L * 5: plane z == 0 * 6: plane z == L """ return BoxMesh( nx, ny, nz, L, L, L, hexahedral=hexahedral, reorder=reorder, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def UnitCubeMesh( nx, ny, nz, hexahedral=False, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a mesh of a unit cube :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 :kwarg hexahedral: (optional), creates hexahedral mesh. :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. The boundary surfaces are numbered as follows: * 1: plane x == 0 * 2: plane x == 1 * 3: plane y == 0 * 4: plane y == 1 * 5: plane z == 0 * 6: plane z == 1 """ return CubeMesh( nx, ny, nz, 1, hexahedral=hexahedral, reorder=reorder, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def PeriodicBoxMesh( nx, ny, nz, Lx, Ly, Lz, directions=(True, True, True), hexahedral=False, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a periodic mesh of a 3D box. Parameters ---------- nx : int Number of cells in the x direction. ny : int Number of cells in the y direction. nz : int Number of cells in the z direction. Lx : float Extent in the x direction. Ly : float Extent in the y direction. Lz : float Extent in the z direction. directions : list or tuple Directions of periodicity. hexahedral : bool Whether to make hexahedral mesh or not. reorder : bool or None Whether to reorder the mesh. distribution_parameters : dict or None Options controlling mesh distribution, see :func:`.Mesh` for details. comm : Communicator to build the mesh on. name : str Name of the mesh. distribution_name : str or None Name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. permutation_name : str or None Name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. Returns ------- MeshGeometry The mesh. Notes ----- The boundary surfaces are numbered as follows: * 1: plane x == 0 * 2: plane x == 1 * 3: plane y == 0 * 4: plane y == 1 * 5: plane z == 0 * 6: plane z == 1 where periodic surfaces are regarded as interior, for which dS integral is to be used. """ for n in (nx, ny, nz): if n < 3: raise ValueError( "3D periodic meshes with fewer than 3 cells are not currently supported" ) if hexahedral: if len(directions) != 3: raise ValueError(f"directions must have exactly dim (=3) elements : Got {directions}") plex = PETSc.DMPlex().createBoxMesh( (nx, ny, nz), lower=(0., 0., 0.), upper=(Lx, Ly, Lz), simplex=False, periodic=directions, interpolate=True, sparseLocalize=False, comm=comm, ) m = mesh.Mesh( plex, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm) x, y, z = SpatialCoordinate(m) V = FunctionSpace(m, "Q", 2) eps = min([Lx / nx, Ly / ny, Lz / nz]) / 1000. if directions[0]: # x fx0 = Function(V).interpolate(conditional(Or(x < eps, x > Lx - eps), 1., 0.)) fx1 = fx0 else: fx0 = Function(V).interpolate(conditional(x < eps, 1., 0.)) fx1 = Function(V).interpolate(conditional(x > Lx - eps, 1., 0.)) if directions[1]: # y fy0 = Function(V).interpolate(conditional(Or(y < eps, y > Ly - eps), 1., 0.)) fy1 = fy0 else: fy0 = Function(V).interpolate(conditional(y < eps, 1., 0.)) fy1 = Function(V).interpolate(conditional(y > Ly - eps, 1., 0.)) if directions[2]: # z fz0 = Function(V).interpolate(conditional(Or(z < eps, z > Lz - eps), 1., 0.)) fz1 = fz0 else: fz0 = Function(V).interpolate(conditional(z < eps, 1., 0.)) fz1 = Function(V).interpolate(conditional(z > Lz - eps, 1., 0.)) return mesh.RelabeledMesh(m, [fx0, fx1, fy0, fy1, fz0, fz1], [1, 2, 3, 4, 5, 6], name=name) else: if tuple(directions) != (True, True, True): raise NotImplementedError("Can only specify directions with hexahedral = True") xcoords = np.arange(0.0, Lx, Lx / nx, dtype=np.double) ycoords = np.arange(0.0, Ly, Ly / ny, dtype=np.double) zcoords = np.arange(0.0, Lz, Lz / nz, dtype=np.double) 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 * ny + j * nx + i v1 = k * nx * ny + j * nx + (i + 1) % nx v2 = k * nx * ny + ((j + 1) % ny) * nx + i v3 = k * nx * ny + ((j + 1) % ny) * nx + (i + 1) % nx v4 = ((k + 1) % nz) * nx * ny + j * nx + i v5 = ((k + 1) % nz) * nx * ny + j * nx + (i + 1) % nx v6 = ((k + 1) % nz) * nx * ny + ((j + 1) % ny) * nx + i v7 = ((k + 1) % nz) * nx * ny + ((j + 1) % ny) * nx + (i + 1) % nx 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).reshape(-1, ny, nx, nz).swapaxes(0, 3).reshape(-1, 4) plex = mesh.plex_from_cell_list( 3, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) m = mesh.Mesh( plex, reorder=reorder_noop, distribution_parameters=distribution_parameters_no_overlap, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) new_coordinates = Function( VectorFunctionSpace( m, FiniteElement("DG", tetrahedron, 1, variant="equispaced") ), name=mesh._generate_default_mesh_coordinates_name(name), ) new_coordinates.interpolate(m.coordinates) coords_by_cell = new_coordinates.dat.data.reshape((-1, 4, 3)).transpose(1, 0, 2) # ensure we really got a view: assert coords_by_cell.base is new_coordinates.dat.data.base # Find the cells that are too big in each direction because they are # wrapped. cell_is_wrapped = ( (coords_by_cell.max(axis=0) - coords_by_cell.min(axis=0)) / (Lx/nx, Ly/ny, Lz/nz) > 1.1 ) # Move wrapped coordinates to the other end of the domain. for i, extent in enumerate((Lx, Ly, Lz)): coords = coords_by_cell[:, :, i] coords[np.logical_and(cell_is_wrapped[:, i], np.isclose(coords, 0))] \ = extent return _postprocess_periodic_mesh(new_coordinates, comm, distribution_parameters, reorder, name, distribution_name, permutation_name)
[docs] @PETSc.Log.EventDecorator() def PeriodicUnitCubeMesh( nx, ny, nz, directions=(True, True, True), hexahedral=False, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a periodic mesh of a unit cube Parameters ---------- nx : int Number of cells in the x direction. ny : int Number of cells in the y direction. nz : int Number of cells in the z direction. directions : list or tuple Directions of periodicity. hexahedral : bool Whether to make hexahedral mesh or not. reorder : bool or None Should the mesh be reordered? distribution_parameters : dict or None Options controlling mesh distribution, see :func:`.Mesh` for details. comm : Communicator to build the mesh on. name : str Name of the mesh. distribution_name : str or None Name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. permutation_name : str or None Name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. Returns ------- MeshGeometry The mesh. Notes ----- The boundary surfaces are numbered as follows: * 1: plane x == 0 * 2: plane x == 1 * 3: plane y == 0 * 4: plane y == 1 * 5: plane z == 0 * 6: plane z == 1 where periodic surfaces are regarded as interior, for which dS integral is to be used. """ return PeriodicBoxMesh( nx, ny, nz, 1.0, 1.0, 1.0, directions=directions, hexahedral=hexahedral, reorder=reorder, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def IcosahedralSphereMesh( radius, refinement_level=0, degree=1, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=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 (e.g., flat triangles if degree=1). :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ 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") 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], ], dtype=np.double, ) # 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.plex_from_cell_list(2, faces, vertices, comm) plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() plex.setName(mesh._generate_default_mesh_topology_name(name)) 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, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) if degree > 1: new_coords = function.Function( functionspace.VectorFunctionSpace(m, "CG", 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, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) m._radius = radius return m
[docs] @PETSc.Log.EventDecorator() def UnitIcosahedralSphereMesh( refinement_level=0, degree=1, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate an icosahedral approximation to the unit sphere. :kwarg refinement_level: optional number of refinements (0 is an icosahedron). :kwarg degree: polynomial degree of coordinate space (e.g., flat triangles if degree=1). :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ return IcosahedralSphereMesh( 1.0, refinement_level=refinement_level, degree=degree, reorder=reorder, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
# mesh is mainly used as a utility, so it's unnecessary to annotate the construction # in this case.
[docs] @PETSc.Log.EventDecorator() @no_annotations def OctahedralSphereMesh( radius, refinement_level=0, degree=1, hemisphere="both", z0=0.8, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate an octahedral 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 an octahedron). :kwarg degree: polynomial degree of coordinate space (e.g., flat triangles if degree=1). :kwarg hemisphere: One of "both", "north", or "south" :kwarg z0: for abs(z/R)>z0, blend from a mesh where the higher-order non-vertex nodes are on lines of latitude to a mesh where these nodes are just pushed out radially from the equivalent P1 mesh. :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ if refinement_level < 0 or refinement_level % 1: raise ValueError("Number of refinements must be a non-negative integer") if degree < 1: raise ValueError("Mesh coordinate degree must be at least 1") if hemisphere not in {"both", "north", "south"}: raise ValueError("Unhandled hemisphere '%s'" % hemisphere) # vertices of an octahedron of radius 1 vertices = np.array( [ [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0], ] ) faces = np.array( [ [0, 1, 2], [0, 1, 5], [0, 2, 4], [0, 4, 5], [1, 2, 3], [1, 3, 5], [2, 3, 4], [3, 4, 5], ], dtype=IntType, ) if hemisphere == "north": vertices = vertices[[0, 1, 2, 3, 4], ...] faces = faces[0::2, ...] elif hemisphere == "south": indices = [0, 1, 3, 4, 5] vertices = vertices[indices, ...] faces = faces[1::2, ...] for new, idx in enumerate(indices): faces[faces == idx] = new plex = mesh.plex_from_cell_list(2, faces, vertices, comm) plex.setRefinementUniform(True) for i in range(refinement_level): plex = plex.refine() plex.setName(mesh._generate_default_mesh_topology_name(name)) # build the initial mesh m = mesh.Mesh( plex, dim=3, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) if degree > 1: # use it to build a higher-order mesh m = assemble(Interpolate(ufl.SpatialCoordinate(m), VectorFunctionSpace(m, "CG", degree))) m = mesh.Mesh( m, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) # remap to a cone x, y, z = ufl.SpatialCoordinate(m) # This will DTWT on meshes with more than 26 refinement levels. # (log_2 1e8 ~= 26.5) tol = ufl.real(Constant(1.0e-8)) rnew = ufl.max_value(1 - abs(z), 0) # Avoid division by zero (when rnew is zero, x & y are also zero) x0 = ufl.conditional(ufl.lt(ufl.real(rnew), tol), 0, x / rnew) y0 = ufl.conditional(ufl.lt(rnew, tol), 0, y / rnew) theta = ufl.conditional( ufl.ge(ufl.real(y0), 0), ufl.pi / 2 * (1 - x0), ufl.pi / 2.0 * (x0 - 1) ) m.coordinates.interpolate( ufl.as_vector([ufl.cos(theta) * rnew, ufl.sin(theta) * rnew, z]) ) # push out to a sphere phi = ufl.pi * z / 2 # Avoid division by zero (when rnew is zero, phi is pi/2, so cos(phi) is zero). scale = ufl.conditional( ufl.lt(ufl.real(rnew), ufl.real(tol)), 0, ufl.cos(phi) / rnew ) znew = ufl.sin(phi) # Make a copy of the coordinates so that we can blend two different # mappings near the pole Vc = m.coordinates.function_space() Xlatitudinal = assemble(Interpolate( Constant(radius) * ufl.as_vector([x * scale, y * scale, znew]), Vc )) Vlow = VectorFunctionSpace(m, "CG", 1) Xlow = assemble(Interpolate(Xlatitudinal, Vlow)) r = ufl.sqrt(Xlow[0] ** 2 + Xlow[1] ** 2 + Xlow[2] ** 2) Xradial = Constant(radius) * Xlow / r s = ufl.real(abs(z) - z0) / (1 - z0) exp = ufl.exp taper = ufl.conditional( ufl.gt(real(s), real(1.0 - tol)), 1.0, ufl.conditional( ufl.gt(real(s), real(tol)), exp(-1.0 / s) / (exp(-1.0 / s) + exp(-1.0 / (1.0 - s))), 0.0 ), ) m.coordinates.interpolate(taper * Xradial + (1 - taper) * Xlatitudinal) m._radius = radius return m
[docs] @PETSc.Log.EventDecorator() def UnitOctahedralSphereMesh( refinement_level=0, degree=1, hemisphere="both", z0=0.8, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate an octahedral approximation to the unit sphere. :kwarg refinement_level: optional number of refinements (0 is an octahedron). :kwarg degree: polynomial degree of coordinate space (e.g., flat triangles if degree=1). :kwarg hemisphere: One of "both", "north", or "south" :kwarg z0: for abs(z)>z0, blend from a mesh where the higher-order non-vertex nodes are on lines of latitude to a mesh where these nodes are just pushed out radially from the equivalent P1 mesh. :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ return OctahedralSphereMesh( 1.0, refinement_level=refinement_level, degree=degree, hemisphere=hemisphere, z0=z0, reorder=reorder, distribution_parameters=distribution_parameters, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
def _cubedsphere_cells_and_coords(radius, refinement_level): """Generate vertex and face lists for cubed sphere""" # We build the mesh out of 6 panels of the cube # this allows to build the gnonomic cube transformation # which is defined separately for each panel # Start by making a grid of local coordinates which we use # to map to each panel of the cubed sphere under the gnonomic # transformation dtheta = 2 ** (-refinement_level + 1) * np.arctan(1.0) a = 3.0 ** (-0.5) * radius theta = np.arange(np.arctan(-1.0), np.arctan(1.0) + dtheta, dtheta, dtype=np.double) x = a * np.tan(theta) Nx = x.size # Compute panel numberings for each panel # We use the following "flatpack" arrangement of panels # 3 # 102 # 4 # 5 # 0 is the bottom of the cube, 5 is the top. # All panels are numbered from left to right, top to bottom # according to this diagram. panel_numbering = np.zeros((6, Nx, Nx), dtype=np.int32) # Numbering for panel 0 panel_numbering[0, :, :] = np.arange(Nx**2, dtype=np.int32).reshape(Nx, Nx) count = panel_numbering.max() + 1 # Numbering for panel 5 panel_numbering[5, :, :] = count + np.arange(Nx**2, dtype=np.int32).reshape( Nx, Nx ) count = panel_numbering.max() + 1 # Numbering for panel 4 - shares top edge with 0 and bottom edge # with 5 # interior numbering panel_numbering[4, 1:-1, :] = count + np.arange( Nx * (Nx - 2), dtype=np.int32 ).reshape(Nx - 2, Nx) # bottom edge panel_numbering[4, 0, :] = panel_numbering[5, -1, :] # top edge panel_numbering[4, -1, :] = panel_numbering[0, 0, :] count = panel_numbering.max() + 1 # Numbering for panel 3 - shares top edge with 5 and bottom edge # with 0 # interior numbering panel_numbering[3, 1:-1, :] = count + np.arange( Nx * (Nx - 2), dtype=np.int32 ).reshape(Nx - 2, Nx) # bottom edge panel_numbering[3, 0, :] = panel_numbering[0, -1, :] # top edge panel_numbering[3, -1, :] = panel_numbering[5, 0, :] count = panel_numbering.max() + 1 # Numbering for panel 1 # interior numbering panel_numbering[1, 1:-1, 1:-1] = count + np.arange( (Nx - 2) ** 2, dtype=np.int32 ).reshape(Nx - 2, Nx - 2) # left edge of 1 is left edge of 5 (inverted) panel_numbering[1, :, 0] = panel_numbering[5, ::-1, 0] # right edge of 1 is left edge of 0 panel_numbering[1, :, -1] = panel_numbering[0, :, 0] # top edge (excluding vertices) of 1 is left edge of 3 (downwards) panel_numbering[1, -1, 1:-1] = panel_numbering[3, -2:0:-1, 0] # bottom edge (excluding vertices) of 1 is left edge of 4 panel_numbering[1, 0, 1:-1] = panel_numbering[4, 1:-1, 0] count = panel_numbering.max() + 1 # Numbering for panel 2 # interior numbering panel_numbering[2, 1:-1, 1:-1] = count + np.arange( (Nx - 2) ** 2, dtype=np.int32 ).reshape(Nx - 2, Nx - 2) # left edge of 2 is right edge of 0 panel_numbering[2, :, 0] = panel_numbering[0, :, -1] # right edge of 2 is right edge of 5 (inverted) panel_numbering[2, :, -1] = panel_numbering[5, ::-1, -1] # bottom edge (excluding vertices) of 2 is right edge of 4 (downwards) panel_numbering[2, 0, 1:-1] = panel_numbering[4, -2:0:-1, -1] # top edge (excluding vertices) of 2 is right edge of 3 panel_numbering[2, -1, 1:-1] = panel_numbering[3, 1:-1, -1] count = panel_numbering.max() + 1 # That's the numbering done. # Set up an array for all of the mesh coordinates Npoints = panel_numbering.max() + 1 coords = np.zeros((Npoints, 3), dtype=np.double) lX, lY = np.meshgrid(x, x) lX.shape = (Nx**2,) lY.shape = (Nx**2,) r = (a**2 + lX**2 + lY**2) ** 0.5 # Now we need to compute the gnonomic transformation # for each of the panels panel_numbering.shape = (6, Nx**2) def coordinates_on_panel(panel_num, X, Y, Z): I = panel_numbering[panel_num, :] coords[I, 0] = radius / r * X coords[I, 1] = radius / r * Y coords[I, 2] = radius / r * Z coordinates_on_panel(0, lX, lY, -a) coordinates_on_panel(1, -a, lY, -lX) coordinates_on_panel(2, a, lY, lX) coordinates_on_panel(3, lX, a, lY) coordinates_on_panel(4, lX, -a, -lY) coordinates_on_panel(5, lX, -lY, a) # Now we need to build the face numbering # in local coordinates vertex_numbers = np.arange(Nx**2, dtype=np.int32).reshape(Nx, Nx) local_faces = np.zeros(((Nx - 1) ** 2, 4), dtype=np.int32) local_faces[:, 0] = vertex_numbers[:-1, :-1].reshape(-1) local_faces[:, 1] = vertex_numbers[1:, :-1].reshape(-1) local_faces[:, 2] = vertex_numbers[1:, 1:].reshape(-1) local_faces[:, 3] = vertex_numbers[:-1, 1:].reshape(-1) cells = panel_numbering[:, local_faces].reshape(-1, 4) return cells, coords
[docs] @PETSc.Log.EventDecorator() def CubedSphereMesh( radius, refinement_level=0, degree=1, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """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 (e.g., bilinear quads if degree=1). :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ 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.plex_from_cell_list( 2, cells, coords, comm, mesh._generate_default_mesh_topology_name(name) ) m = mesh.Mesh( plex, dim=3, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) 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, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) m._radius = radius return m
[docs] @PETSc.Log.EventDecorator() def UnitCubedSphereMesh( refinement_level=0, degree=1, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a cubed approximation to the unit sphere. :kwarg refinement_level: optional number of refinements (0 is a cube). :kwarg degree: polynomial degree of coordinate space (e.g., bilinear quads if degree=1). :kwarg reorder: (optional), should the mesh be reordered? :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ return CubedSphereMesh( 1.0, refinement_level=refinement_level, degree=degree, reorder=reorder, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, )
[docs] @PETSc.Log.EventDecorator() def TorusMesh( nR, nr, R, r, quadrilateral=False, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=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. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. """ if nR < 3 or nr < 3: raise ValueError("Must have at least 3 cells in each direction") for n in (nR, nr): if n % 1: raise RuntimeError("Number of cells must be an integer") # 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.asarray( 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)), ) ), dtype=np.double, ) # cell vertices i, j = np.meshgrid(np.arange(nR, dtype=np.int32), np.arange(nr, dtype=np.int32)) 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.plex_from_cell_list( 2, cells, vertices, comm, mesh._generate_default_mesh_topology_name(name) ) m = mesh.Mesh( plex, dim=3, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, ) return m
[docs] @PETSc.Log.EventDecorator() def AnnulusMesh( R, r, nr=4, nt=32, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate an annulus mesh periodically extruding an interval mesh :arg R: The outer radius :arg r: The inner radius :kwarg nr: (optional), number of cells in the radial direction :kwarg nt: (optional), number of cells in the circumferential direction (min 3) :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if ``None``, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if ``None``, the name is automatically generated. """ if nt < 3: raise ValueError("Must have at least 3 cells in the circumferential direction") base_name = name + "_base" base = IntervalMesh(nr, r, right=R, distribution_parameters=distribution_parameters, comm=comm, name=base_name, distribution_name=distribution_name, permutation_name=permutation_name) bar = mesh.ExtrudedMesh(base, layers=nt, layer_height=2 * np.pi / nt, extrusion_type="uniform", periodic=True) x, y = ufl.SpatialCoordinate(bar) V = bar.coordinates.function_space() coord = Function(V).interpolate(ufl.as_vector([x * ufl.cos(y), x * ufl.sin(y)])) annulus = mesh.make_mesh_from_coordinates(coord.topological, name) annulus.topology.name = mesh._generate_default_mesh_topology_name(name) annulus._base_mesh = base return annulus
[docs] @PETSc.Log.EventDecorator() def SolidTorusMesh( R, r, nR=8, refinement_level=0, reorder=None, distribution_parameters=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a solid toroidal mesh (with axis z) periodically extruding a disk mesh :arg R: The major radius :arg r: The minor radius :kwarg nR: (optional), number of cells in the major direction (min 3) :kwarg refinement_level: (optional), number of times the base disk mesh is refined. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if ``None``, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if ``None``, the name is automatically generated. """ if nR < 3: raise ValueError("Must have at least 3 cells in the major direction") base_name = name + "_base" unit = UnitDiskMesh(refinement_level=refinement_level, reorder=reorder, distribution_parameters=distribution_parameters, comm=comm, distribution_name=distribution_name, permutation_name=permutation_name) x, y = ufl.SpatialCoordinate(unit) V = unit.coordinates.function_space() coord = Function(V).interpolate(ufl.as_vector([r * x + R, r * y])) disk = mesh.make_mesh_from_coordinates(coord.topological, base_name) disk.topology.name = mesh._generate_default_mesh_topology_name(base_name) disk.topology.topology_dm.setName(disk.topology.name) bar = mesh.ExtrudedMesh(disk, layers=nR, layer_height=2 * np.pi / nR, extrusion_type="uniform", periodic=True) x, y, z = ufl.SpatialCoordinate(bar) V = bar.coordinates.function_space() coord = Function(V).interpolate(ufl.as_vector([x * ufl.cos(z), x * ufl.sin(z), -y])) torus = mesh.make_mesh_from_coordinates(coord.topological, name) torus.topology.name = mesh._generate_default_mesh_topology_name(name) torus._base_mesh = disk return torus
[docs] @PETSc.Log.EventDecorator() def CylinderMesh( nr, nl, radius=1, depth=1, longitudinal_direction="z", quadrilateral=False, reorder=None, distribution_parameters=None, diagonal=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """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. :kwarg depth: (optional) depth of the cylinder to approximate. :kwarg longitudinal_direction: (option) direction for the longitudinal axis of the cylinder. :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: (optional), one of ``"crossed"``, ``"left"``, ``"right"``. Not valid for quad meshes. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. 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") if quadrilateral and diagonal is not None: raise ValueError("Cannot specify slope of diagonal on quad meshes") if not quadrilateral and diagonal is None: diagonal = "left" 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)) if not quadrilateral and diagonal == "crossed": dxy = np.pi / nr Lxy = 2 * np.pi extra_uv = np.linspace(dxy, Lxy - dxy, nr, dtype=np.double) extra_xy = radius * np.column_stack((np.cos(extra_uv), np.sin(extra_uv))) dz = 1 * 0.5 / nl extra_z = depth * np.linspace(dz, 1 - dz, nl).reshape(-1, 1) extras = np.asarray( np.column_stack( (np.tile(extra_xy, (nl, 1)), np.tile(extra_z, (1, nr)).reshape(-1, 1)) ), dtype=np.double, ) origvertices = vertices vertices = np.vstack([vertices, extras]) # # 2-----3 # | \ / | # | 4 | # | / \ | # 0-----1 offset = np.arange(nl, dtype=np.int32) * nr origquads = np.row_stack(tuple(ring_cells + i for i in offset)) cells = np.zeros((origquads.shape[0] * 4, 3), dtype=np.int32) cellidx = 0 newvertices = range(len(origvertices), len(origvertices) + len(extras)) for (origquad, extravertex) in zip(origquads, newvertices): cells[cellidx + 0, :] = [origquad[0], origquad[1], extravertex] cells[cellidx + 1, :] = [origquad[0], origquad[3], extravertex] cells[cellidx + 2, :] = [origquad[3], origquad[2], extravertex] cells[cellidx + 3, :] = [origquad[2], origquad[1], extravertex] cellidx += 4 else: offset = np.arange(nl, dtype=np.int32) * nr cells = np.row_stack(tuple(ring_cells + i for i in offset)) 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) 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.plex_from_cell_list( 2, cells, vertices, comm, mesh._generate_default_mesh_topology_name(name) ) plex.createLabel(dmcommon.FACE_SETS_LABEL) 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(dmcommon.FACE_SETS_LABEL, face, 1) if abs(face_coords[i] - depth) < eps and abs(face_coords[j] - depth) < eps: # top of cylinder plex.setLabelValue(dmcommon.FACE_SETS_LABEL, face, 2) plex.removeLabel("boundary_faces") return mesh.Mesh( plex, dim=3, reorder=reorder, distribution_parameters=distribution_parameters, name=name, distribution_name=distribution_name, permutation_name=permutation_name, comm=comm, )
@PETSc.Log.EventDecorator() def PartiallyPeriodicRectangleMesh( nx, ny, Lx, Ly, direction="x", quadrilateral=False, reorder=None, distribution_parameters=None, diagonal=None, comm=COMM_WORLD, name=mesh.DEFAULT_MESH_NAME, distribution_name=None, permutation_name=None, ): """Generate a 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. :kwarg quadrilateral: (optional), creates quadrilateral mesh. :kwarg reorder: (optional), should the mesh be reordered :kwarg distribution_parameters: options controlling mesh distribution, see :func:`.Mesh` for details. :kwarg diagonal: (optional), one of ``"crossed"``, ``"left"``, ``"right"``. Not valid for quad meshes. :kwarg comm: Optional communicator to build the mesh on. :kwarg name: Optional name of the mesh. :kwarg distribution_name: the name of parallel distribution used when checkpointing; if `None`, the name is automatically generated. :kwarg permutation_name: the name of entity permutation (reordering) used when checkpointing; if `None`, the name is automatically generated. 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 = nx, ny if direction == "y": na, nb = ny, nx 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_noop, distribution_parameters=distribution_parameters_no_overlap, diagonal=diagonal, comm=comm, name=name, distribution_name=distribution_name, permutation_name=permutation_name, ) coord_family = "DQ" if quadrilateral else "DG" cell = "quadrilateral" if quadrilateral else "triangle" indicator = Function(FunctionSpace(m, coord_family, 0)) coord_fs = VectorFunctionSpace( m, FiniteElement(coord_family, cell, 1, variant="equispaced"), dim=2 ) new_coordinates = Function( coord_fs, name=mesh._generate_default_mesh_coordinates_name(name) ) x, y, z = SpatialCoordinate(m) eps = 1.e-14 indicator.interpolate(conditional(gt(real(y), 0), 0., 1.)) if direction == "x": transform = as_tensor([[Lx, 0.], [0., Ly]]) else: transform = as_tensor([[0., Lx], [Ly, 0]]) new_coordinates.interpolate(dot( transform, as_vector(( conditional(gt(real(x), real(1. - eps)), indicator, # Periodic break. # Unwrap rest of circle. atan2(real(-y), real(-x))/(2 * pi) + 0.5), z )) )) return _postprocess_periodic_mesh(new_coordinates, comm, distribution_parameters, reorder, name, distribution_name, permutation_name)