Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
    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()