Example #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
Example #2
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)
Example #3
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)
Example #4
0
def get_affine_reference_simplex_mapping(ambient_dim, firedrake_to_meshmode=True):
    """
    Returns a function which takes a numpy array points
    on one reference cell and maps each
    point to another using a positive affine map.

    :arg ambient_dim: The spatial dimension
    :arg firedrake_to_meshmode: If true, the returned function maps from
        the firedrake reference element to
        meshmode, if false maps from
        meshmode to firedrake. More specifically,
        :mod:`firedrake` uses the standard :mod:`FIAT`
        simplex and :mod:`meshmode` uses
        :mod:`modepy`'s
        `unit coordinates <https://documen.tician.de/modepy/nodes.html>`_.
    :return: A function which takes a numpy array of *n* points with
             shape *(dim, n)* on one reference cell and maps
             each point to another using a positive affine map.
             Note that the returned function performs
             no input validation.
    """
    # validate input
    if not isinstance(ambient_dim, int):
        raise TypeError("'ambient_dim' must be an int, not "
                        f"'{type(ambient_dim)}'")
    if ambient_dim < 0:
        raise ValueError("'ambient_dim' must be non-negative")
    if not isinstance(firedrake_to_meshmode, bool):
        raise TypeError("'firedrake_to_meshmode' must be a bool, not "
                        f"'{type(firedrake_to_meshmode)}'")

    from FIAT.reference_element import ufc_simplex
    from modepy.tools import unit_vertices
    # Get the unit vertices from each system,
    # each stored with shape *(dim, nunit_vertices)*
    firedrake_unit_vertices = np.array(ufc_simplex(ambient_dim).vertices).T
    modepy_unit_vertices = unit_vertices(ambient_dim).T

    if firedrake_to_meshmode:
        from_verts = firedrake_unit_vertices
        to_verts = modepy_unit_vertices
    else:
        from_verts = modepy_unit_vertices
        to_verts = firedrake_unit_vertices

    # Compute matrix A and vector b so that A f_i + b -> t_i
    # for each "from" vertex f_i and corresponding "to" vertex t_i
    assert from_verts.shape == to_verts.shape
    dim, nvects = from_verts.shape

    # If we only have one vertex, have A = I and b = to_vert - from_vert
    if nvects == 1:
        shift = to_verts[:, 0] - from_verts[:, 0]

        def affine_map(points):
            return points + shift[:, np.newaxis]
    # Otherwise, we have to solve for A and b
    else:
        # span verts: v1 - v0, v2 - v0, ...
        from_span_verts = from_verts[:, 1:] - from_verts[:, 0, np.newaxis]
        to_span_verts = to_verts[:, 1:] - to_verts[:, 0, np.newaxis]
        # mat maps (fj - f0) -> (tj - t0), our "A"
        mat = la.solve(from_span_verts, to_span_verts)
        # A f0 + b -> t0 so b = t0 - A f0
        shift = to_verts[:, 0] - np.matmul(mat, from_verts[:, 0])

        # Explicitly ensure A is positive
        if la.det(mat) < 0:
            from meshmode.mesh.processing import get_simplex_element_flip_matrix
            flip_matrix = get_simplex_element_flip_matrix(1, to_verts)
            mat = np.matmul(flip_matrix, mat)

        def affine_map(points):
            return np.matmul(mat, points) + shift[:, np.newaxis]

    return affine_map