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 import_firedrake_mesh(fdrake_mesh, cells_to_use=None, normals=None, no_normals_warn=None): """ Create a :class:`meshmode.mesh.Mesh` from a `firedrake.mesh.MeshGeometry` with the same cells/elements, vertices, nodes, mesh order, and facial adjacency. The vertex and node coordinates will be the same, as well as the cell/element ordering. However, :mod:`firedrake` does not require elements to be positively oriented, so any negative elements are flipped as in :func:`meshmode.mesh.processing.flip_simplex_element_group`. The flipped cells/elements are identified by the returned *firedrake_orient* array :arg fdrake_mesh: `firedrake.mesh.MeshGeometry`. This mesh **must** be in a space of ambient dimension 1, 2, or 3 and have co-dimension of 0 or 1. It must use a simplex as a reference element. In the case of a 2-dimensional mesh embedded in 3-space, the method ``fdrake_mesh.init_cell_orientations`` must have been called. In the case of a 1-dimensional mesh embedded in 2-space, see parameters *normals* and *no_normals_warn*. Finally, the ``coordinates`` attribute must have a function space whose *finat_element* associates a degree of freedom with each vertex. In particular, this means that the vertices of the mesh must have well-defined coordinates. For those unfamiliar with :mod:`firedrake`, you can verify this by looking at .. code-block:: python coords_fspace = fdrake_mesh.coordinates.function_space() vertex_entity_dofs = coords_fspace.finat_element.entity_dofs()[0] for entity, dof_list in vertex_entity_dofs.items(): assert len(dof_list) > 0 :arg cells_to_use: *cells_to_use* is primarily intended for use internally by :func:`~meshmode.interop.firedrake.connection.\ build_connection_from_firedrake`. *cells_to_use* must be either 1. *None*, in which case this argument is ignored, or 2. a numpy array of unique firedrake cell indexes. In case (2.), only cells whose index appears in *cells_to_use* are included in the resultant mesh, and their index in *cells_to_use* becomes the element index in the resultant mesh element group. Any faces or vertices which do not touch a cell in *cells_to_use* are also ignored. Note that in this latter case, some faces that are not boundaries in *fdrake_mesh* may become boundaries in the returned mesh. These "induced" boundaries are marked with :class:`~meshmode.mesh.BTAG_INDUCED_BOUNDARY` instead of :class:`~meshmode.mesh.BTAG_ALL`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, - If *None* then all elements are assumed to be positively oriented. - Else, should be a list/array whose *i*\\ th entry is the normal for the *i*\\ th element (*i*\\ th in *mesh.coordinate.function_space()*'s *cell_node_list*) :arg no_normals_warn: If *True* (the default), raises a warning if *fdrake_mesh* is a 1-surface embedded in 2-space and *normals* is *None*. :return: A tuple *(meshmode mesh, firedrake_orient)*. ``firedrake_orient < 0`` is *True* for any negatively oriented firedrake cell (which was flipped by meshmode) and False for any positively oriented firedrake cell (which was not flipped by meshmode). """ # Type validation from firedrake.mesh import MeshGeometry if not isinstance(fdrake_mesh, MeshGeometry): raise TypeError("'fdrake_mesh_topology' must be an instance of " "firedrake.mesh.MeshGeometry, " "not '%s'." % type(fdrake_mesh)) if cells_to_use is not None: if not isinstance(cells_to_use, np.ndarray): raise TypeError("'cells_to_use' must be a np.ndarray or " "*None*") assert len(cells_to_use.shape) == 1 assert np.size(np.unique(cells_to_use)) == np.size(cells_to_use), \ ":arg:`cells_to_use` must have unique entries" assert np.all(np.logical_and(cells_to_use >= 0, cells_to_use < fdrake_mesh.num_cells())) assert fdrake_mesh.ufl_cell().is_simplex(), "Mesh must use simplex cells" gdim = fdrake_mesh.geometric_dimension() tdim = fdrake_mesh.topological_dimension() assert gdim in [1, 2, 3], "Mesh must be in space of ambient dim 1, 2, or 3" assert gdim - tdim in [0, 1], "Mesh co-dimension must be 0 or 1" # firedrake meshes are not guaranteed be fully instantiated until # the .init() method is called. In particular, the coordinates function # may not be accessible if we do not call init(). If the mesh has # already been initialized, nothing will change. For more details # on why we need a second initialization, see # this pull request: # https://github.com/firedrakeproject/firedrake/pull/627 # which details how Firedrake implements a mesh's coordinates # as a function on that very same mesh fdrake_mesh.init() # Get all the nodal information we can from the topology bdy_tags = _get_firedrake_boundary_tags( fdrake_mesh, tag_induced_boundary=cells_to_use is not None) with ProcessLogger(logger, "Retrieving vertex indices and computing " "NodalAdjacency from firedrake mesh"): vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) # If only using some cells, vertices may need new indices as many # will be removed if cells_to_use is not None: vert_ndx_new2old = np.unique(vertex_indices.flatten()) vert_ndx_old2new = dict(zip(vert_ndx_new2old, np.arange(np.size(vert_ndx_new2old), dtype=vertex_indices.dtype))) vertex_indices = \ np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) with ProcessLogger(logger, "Building (possibly) unflipped " "SimplexElementGroup from firedrake unit nodes/nodes"): # Grab the mesh reference element and cell dimension coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element cell_dim = fdrake_mesh.cell_dimension() # Get finat unit nodes and map them onto the meshmode reference simplex finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) # Now grab the nodes coords = fdrake_mesh.coordinates cell_node_list = coords.function_space().cell_node_list if cells_to_use is not None: cell_node_list = cell_node_list[cells_to_use] nodes = np.real(coords.dat.data[cell_node_list]) # Add extra dim in 1D for shape (nelements, nunit_nodes, dim) if tdim == 1: nodes = np.reshape(nodes, nodes.shape + (1,)) # Transpose nodes to have shape (dim, nelements, nunit_nodes) nodes = np.transpose(nodes, (2, 0, 1)) # make a group (possibly with some elements that need to be flipped) unflipped_group = SimplexElementGroup(coord_finat_elt.degree, vertex_indices, nodes, dim=cell_dim, unit_nodes=finat_unit_nodes) # Next get the vertices (we'll need these for the orientations) with ProcessLogger(logger, "Obtaining vertex coordinates"): coord_finat = fdrake_mesh.coordinates.function_space().finat_element # unit_vertex_indices are the element-local indices of the nodes # which coincide with the vertices, i.e. for element *i*, # vertex 0's coordinates would be nodes[i][unit_vertex_indices[0]]. # This assumes each vertex has some node which coincides with it... # which is normally fine to assume for firedrake meshes. unit_vertex_indices = [] # iterate through the dofs associated to each vertex on the # reference element for _, dofs in sorted(coord_finat.entity_dofs()[0].items()): assert len(dofs) == 1, \ "The function space of the mesh coordinates must have" \ " exactly one degree of freedom associated with " \ " each vertex in order to determine vertex coordinates" dof, = dofs unit_vertex_indices.append(dof) # Now get the vertex coordinates as *(dim, nvertices)*-shaped array if cells_to_use is not None: nvertices = np.size(vert_ndx_new2old) else: nvertices = fdrake_mesh.num_vertices() vertices = np.ndarray((gdim, nvertices), dtype=nodes.dtype) recorded_verts = set() for icell, cell_vertex_indices in enumerate(vertex_indices): for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): if global_vert_id not in recorded_verts: recorded_verts.add(global_vert_id) local_node_nr = unit_vertex_indices[local_vert_id] vertices[:, global_vert_id] = nodes[:, icell, local_node_nr] # Use the vertices to compute the orientations and flip the group with ProcessLogger(logger, "Computing cell orientations"): orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, cells_to_use=cells_to_use, normals=normals, no_normals_warn=no_normals_warn) with ProcessLogger(logger, "Flipping group"): from meshmode.mesh.processing import flip_simplex_element_group group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) # Now, any flipped element had its 0 vertex and 1 vertex exchanged. # This changes the local facet nr, so we need to create and then # fix our facial adjacency groups. To do that, we need to figure # out which local facet numbers switched. face_vertex_indices = group.face_vertex_indices() # face indices of the faces not containing vertex 0 and not # containing vertex 1, respectively no_zero_face_ndx, no_one_face_ndx = None, None for iface, face in enumerate(face_vertex_indices): if 0 not in face: no_zero_face_ndx = iface elif 1 not in face: no_one_face_ndx = iface with ProcessLogger(logger, "Building (possibly) unflipped " "FacialAdjacencyGroups"): unflipped_facial_adjacency_groups = \ _get_firedrake_facial_adjacency_groups(fdrake_mesh, cells_to_use=cells_to_use) # applied below to take elements and element_faces # (or neighbors and neighbor_faces) and flip in any faces that need to # be flipped. def flip_local_face_indices(faces, elements): faces = np.copy(faces) neg_elements = np.full(elements.shape, False) # To handle neighbor case, we only need to flip at elements # who have a neighbor, i.e. where neighbors is not a negative # bitmask of bdy tags neg_elements[elements >= 0] = (orient[elements[elements >= 0]] < 0) no_zero = np.logical_and(neg_elements, faces == no_zero_face_ndx) no_one = np.logical_and(neg_elements, faces == no_one_face_ndx) faces[no_zero], faces[no_one] = no_one_face_ndx, no_zero_face_ndx return faces # Create new facial adjacency groups that have been flipped with ProcessLogger(logger, "Flipping FacialAdjacencyGroups"): facial_adjacency_groups = [] for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): facial_adjacency_groups.append({}) for ineighbor_group, fagrp in fagrps.items(): new_element_faces = flip_local_face_indices(fagrp.element_faces, fagrp.elements) new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, fagrp.neighbors) new_fagrp = FacialAdjacencyGroup(igroup=igroup, ineighbor_group=ineighbor_group, elements=fagrp.elements, element_faces=new_element_faces, neighbors=fagrp.neighbors, neighbor_faces=new_neighbor_faces) facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp return (Mesh(vertices, [group], boundary_tags=bdy_tags, nodal_adjacency=nodal_adjacency, facial_adjacency_groups=facial_adjacency_groups), orient)
def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, restrict_to_boundary=None): """ Create a :class:`FiredrakeConnection` from a :mod:`firedrake` ``"DG"`` function space by creates a corresponding meshmode discretization and facilitating transfer of functions to and from :mod:`firedrake`. :arg actx: A :class:`~meshmode.array_context.ArrayContext` used to instantiate :attr:`FiredrakeConnection.discr`. :arg fdrake_fspace: A :mod:`firedrake` ``"DG"`` function space (of class :class:`~firedrake.functionspaceimpl.WithGeometry`) built on a mesh which is importable by :func:`~meshmode.interop.firedrake.mesh.import_firedrake_mesh`. :arg grp_factory: (optional) If not *None*, should be a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` whose group class is a subclass of :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. If *None*, and :mod:`recursivenodes` can be imported, a :class:`~meshmode.discretization.poly_element.\ PolynomialRecursiveNodesGroupFactory` with ``"lgl"`` nodes is used. Note that :mod:`recursivenodes` may not be importable as it uses :func:`math.comb`, which is new in Python 3.8. In the case that :mod:`recursivenodes` cannot be successfully imported, a :class:`~meshmode.discretization.poly_element.\ PolynomialWarpAndBlendGroupFactory` is used. :arg restrict_to_boundary: (optional) If not *None*, then must be a valid boundary marker for ``fdrake_fspace.mesh()``. In this case, creates a :class:`~meshmode.discretization.Discretization` on a submesh of ``fdrake_fspace.mesh()`` created from the cells with at least one vertex on a facet marked with the marker *restrict_to_boundary*. """ # Ensure fdrake_fspace is a function space with appropriate reference # element. from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): raise TypeError("'fdrake_fspace' must be of firedrake type " "WithGeometry, not '%s'." % type(fdrake_fspace)) ufl_elt = fdrake_fspace.ufl_element() if ufl_elt.family() != "Discontinuous Lagrange": raise ValueError("the 'fdrake_fspace.ufl_element().family()' of " "must be be " "'Discontinuous Lagrange', not '%s'." % ufl_elt.family()) # Make sure grp_factory is the right type if provided, and # uses an interpolatory class. if grp_factory is not None: if not isinstance(grp_factory, ElementGroupFactory): raise TypeError("'grp_factory' must inherit from " "meshmode.discretization.ElementGroupFactory," "but is instead of type " "'%s'." % type(grp_factory)) if not issubclass(grp_factory.group_class, InterpolatoryElementGroupBase): raise TypeError("'grp_factory.group_class' must inherit from" "meshmode.discretization." "InterpolatoryElementGroupBase, but" " is instead of type '%s'" % type(grp_factory.group_class)) # If not provided, make one else: degree = ufl_elt.degree() try: # recursivenodes is only importable in Python 3.8 since # it uses :func:`math.comb`, so need to check if it can # be imported import recursivenodes # noqa : F401 family = "lgl" # L-G-Legendre grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) except ImportError: # If cannot be imported, uses warp-and-blend nodes grp_factory = PolynomialWarpAndBlendGroupFactory(degree) if restrict_to_boundary is not None: uniq_markers = fdrake_fspace.mesh().exterior_facets.unique_markers allowable_bdy_ids = list(uniq_markers) + ["on_boundary"] if restrict_to_boundary not in allowable_bdy_ids: raise ValueError("'restrict_to_boundary' must be one of" " the following allowable boundary ids: " f"{allowable_bdy_ids}, not " f"'{restrict_to_boundary}'") # If only converting a portion of the mesh near the boundary, get # *cells_to_use* as described in # :func:`meshmode.interop.firedrake.mesh.import_firedrake_mesh` cells_to_use = _get_cells_to_use(fdrake_fspace.mesh(), restrict_to_boundary) # Create to_discr mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), cells_to_use=cells_to_use) to_discr = Discretization(actx, mm_mesh, grp_factory) # get firedrake unit nodes and map onto meshmode reference element group = to_discr.groups[0] fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) # Flipping negative elements corresponds to reordering the nodes. # We handle reordering by storing the permutation explicitly as # a numpy array # Get the reordering fd->mm. flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list if cells_to_use is not None: fd_cell_node_list = fd_cell_node_list[cells_to_use] # flip fd_cell_node_list flipped_cell_node_list = _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) assert np.size(np.unique(flipped_cell_node_list)) == \ np.size(flipped_cell_node_list), \ "A firedrake node in a 'DG' space got duplicated" return FiredrakeConnection(to_discr, fdrake_fspace, flipped_cell_node_list)
def build_connection_to_firedrake(discr, group_nr=None, comm=None): """ Create a connection from a meshmode discretization into firedrake. Create a corresponding "DG" function space and allow for conversion back and forth by resampling at the nodes. :param discr: A :class:`~meshmode.discretization.Discretization` to intialize the connection with :param group_nr: The group number of the discretization to convert. If *None* there must be only one group. The selected group must be of type :class:`~meshmode.discretization.poly_element.\ InterpolatoryQuadratureSimplexElementGroup`. :param comm: Communicator to build a dmplex object on for the created firedrake mesh """ if group_nr is None: if len(discr.groups) != 1: raise ValueError("'group_nr' is *None*, but 'discr' has '%s' " "!= 1 groups." % len(discr.groups)) group_nr = 0 el_group = discr.groups[group_nr] from firedrake.functionspace import FunctionSpace fd_mesh, fd_cell_order, perm2cells = \ export_mesh_to_firedrake(discr.mesh, group_nr, comm) fspace = FunctionSpace(fd_mesh, "DG", el_group.order) # get firedrake unit nodes and map onto meshmode reference element dim = fspace.mesh().topological_dimension() fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(dim, True) fd_unit_nodes = get_finat_element_unit_nodes(fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) # **_cell_node holds the node nrs in shape *(ncells, nunit_nodes)* fd_cell_node = fspace.cell_node_list # To get the meshmode to firedrake node assocation, we need to handle # local vertex reordering and cell reordering. from pyop2.datatypes import IntType mm2fd_node_mapping = np.ndarray((el_group.nelements, el_group.nunit_dofs), dtype=IntType) for perm, cells in perm2cells.items(): # reordering_arr[i] should be the fd node corresponding to meshmode # node i # # The jth meshmode cell corresponds to the fd_cell_order[j]th # firedrake cell. If *nodeperm* is the permutation of local nodes # applied to the *j*\ th meshmode cell, the firedrake node # fd_cell_node[fd_cell_order[j]][k] corresponds to the # mm_cell_node[j, nodeperm[k]]th meshmode node. # # Note that the permutation on the unit nodes may not be the # same as the permutation on the barycentric coordinates (*perm*). # Importantly, the permutation is derived from getting a flip # matrix from the Firedrake unit nodes, not necessarily the meshmode # unit nodes # flip_mat = get_simplex_element_flip_matrix(el_group.order, fd_unit_nodes, np.argsort(perm)) flip_mat = np.rint(flip_mat).astype(IntType) fd_permuted_cell_node = np.matmul(fd_cell_node[fd_cell_order[cells]], flip_mat.T) mm2fd_node_mapping[cells] = fd_permuted_cell_node assert np.size(np.unique(mm2fd_node_mapping)) == \ np.size(mm2fd_node_mapping), \ "A firedrake node in a 'DG' space got duplicated" return FiredrakeConnection(discr, fspace, mm2fd_node_mapping, group_nr=group_nr)
def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ :param discr: A :class:`meshmode.discretization.Discretization` :param fdrake_fspace: A :class:`firedrake.functionspaceimpl.WithGeometry`. Must use ufl family ``"Discontinuous Lagrange"``. :param mm2fd_node_mapping: Used as attribute :attr:`mm2fd_node_mapping`. A 2-D numpy integer array with the same dtype as ``fdrake_fspace.cell_node_list.dtype`` :param group_nr: The index of the group in *discr* which is being connected to *fdrake_fspace*. The group must be a :class:`~meshmode.discretization.InterpolatoryElementGroupBase` of the same topological dimension as *fdrake_fspace*. If *discr* has only one group, *group_nr=None* may be supplied. :raises TypeError: If any input arguments are of the wrong type, if the designated group is of the wrong type, or if *fdrake_fspace* is of the wrong family. :raises ValueError: If *mm2fd_node_mapping* is of the wrong shape or dtype, if *group_nr* is an invalid index, or if *group_nr* is *None* when *discr* has more than one group. """ # {{{ Validate input if not isinstance(discr, Discretization): raise TypeError("'discr' must be of type " "meshmode.discretization.Discretization, " "not '%s'`." % type(discr)) from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): raise TypeError("'fdrake_fspace' must be of type " "firedrake.functionspaceimpl.WithGeometry, " "not '%s'." % type(fdrake_fspace)) if not isinstance(mm2fd_node_mapping, np.ndarray): raise TypeError("'mm2fd_node_mapping' must be of type " "numpy.ndarray, " "not '%s'." % type(mm2fd_node_mapping)) if not isinstance(group_nr, int) and group_nr is not None: raise TypeError("'group_nr' must be of type int or be " "*None*, not of type '%s'." % type(group_nr)) # Convert group_nr to an integer if *None* if group_nr is None: if len(discr.groups) != 1: raise ValueError("'group_nr' is *None* but 'discr' " "has '%s' != 1 groups." % len(discr.groups)) group_nr = 0 # store element_grp as variable for convenience element_grp = discr.groups[group_nr] if group_nr < 0 or group_nr >= len(discr.groups): raise ValueError("'group_nr' has value '%s', which an invalid " "index into list 'discr.groups' of length '%s'." % (group_nr, len(discr.groups))) if not isinstance(element_grp, InterpolatoryElementGroupBase): raise TypeError("'discr.groups[group_nr]' must be of type " "InterpolatoryElementGroupBase" ", not '%s'." % type(element_grp)) if fdrake_fspace.ufl_element().family() != "Discontinuous Lagrange": raise TypeError("'fdrake_fspace.ufl_element().family()' must be" "'Discontinuous Lagrange', not " f"'{fdrake_fspace.ufl_element().family()}'") if mm2fd_node_mapping.shape != (element_grp.nelements, element_grp.nunit_dofs): raise ValueError("'mm2fd_node_mapping' must be of shape ", "(%s,), not '%s'" % ((discr.groups[group_nr].ndofs,), mm2fd_node_mapping.shape)) if mm2fd_node_mapping.dtype != fdrake_fspace.cell_node_list.dtype: raise ValueError("'mm2fd_node_mapping' must have dtype " "%s, not '%s'" % (fdrake_fspace.cell_node_list.dtype, mm2fd_node_mapping.dtype)) if np.size(np.unique(mm2fd_node_mapping)) != np.size(mm2fd_node_mapping): raise ValueError("'mm2fd_node_mapping' must have unique entries; " "no two meshmode nodes may be associated to the " "same Firedrake node") # }}} # Get meshmode unit nodes mm_unit_nodes = element_grp.unit_nodes # get firedrake unit nodes and map onto meshmode reference element tdim = fdrake_fspace.mesh().topological_dimension() fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(tdim, True) fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) # compute and store resampling matrices self._resampling_mat_fd2mm = resampling_matrix(element_grp.basis(), new_nodes=mm_unit_nodes, old_nodes=fd_unit_nodes) self._resampling_mat_mm2fd = resampling_matrix(element_grp.basis(), new_nodes=fd_unit_nodes, old_nodes=mm_unit_nodes) # Store input self.discr = discr self.group_nr = group_nr self.mm2fd_node_mapping = mm2fd_node_mapping self._mesh_geometry = fdrake_fspace.mesh() self._ufl_element = fdrake_fspace.ufl_element()