Пример #1
0
    def facial_adjacency_groups(self):
        """
            Return a :mod:`meshmode` list of :class:`FacialAdjacencyGroups`
            as used in the construction of a :mod:`meshmode` :class:`Mesh`
        """
        # {{{ Compute facial adjacency groups if not already done

        if self._facial_adjacency_groups is None:
            from meshmode.mesh import _compute_facial_adjacency_from_vertices

            self._facial_adjacency_groups = _compute_facial_adjacency_from_vertices(
                [self.group()],
                self.bdy_tags(),
                np.int32, np.int8,
                face_vertex_indices_to_tags=self.face_vertex_indices_to_tags())

        # }}}

        return self._facial_adjacency_groups
Пример #2
0
def generate_box_mesh(axis_coords,
                      order=1,
                      coord_dtype=np.float64,
                      group_cls=None,
                      boundary_tag_to_face=None,
                      mesh_type=None):
    r"""Create a semi-structured mesh.

    :param axis_coords: a tuple with a number of entries corresponding
        to the number of dimensions, with each entry a numpy array
        specifying the coordinates to be used along that axis.
    :param group_cls: One of :class:`meshmode.mesh.SimplexElementGroup`
        or :class:`meshmode.mesh.TensorProductElementGroup`.
    :param boundary_tag_to_face: an optional dictionary for tagging boundaries.
        The keys correspond to custom boundary tags, with the values giving
        a list of the faces on which they should be applied in terms of coordinate
        directions (``+x``, ``-x``, ``+y``, ``-y``, ``+z``, ``-z``, ``+w``, ``-w``).

        For example::

            boundary_tag_to_face={"bdry_1": ["+x", "+y"], "bdry_2": ["-x"]}
    :param mesh_type: In two dimensions with non-tensor-product elements,
        *mesh_type* may be set to ``"X"`` to generate this type
        of mesh::

            _______
            |\   /|
            | \ / |
            |  X  |
            | / \ |
            |/   \|
            ^^^^^^^

        instead of the default::

            _______
            |\    |
            | \   |
            |  \  |
            |   \ |
            |    \|
            ^^^^^^^

        Specifying a value other than *None* for all other mesh
        dimensionalities and element types is an error.

    .. versionchanged:: 2017.1

        *group_factory* parameter added.

    .. versionchanged:: 2020.1

        *boundary_tag_to_face* parameter added.

    .. versionchanged:: 2020.3

        *group_factory* deprecated and renamed to *group_cls*.
    """

    if boundary_tag_to_face is None:
        boundary_tag_to_face = {}

    for iaxis, axc in enumerate(axis_coords):
        if len(axc) < 2:
            raise ValueError("need at least two points along axis %d" %
                             (iaxis + 1))

    dim = len(axis_coords)

    shape = tuple(len(axc) for axc in axis_coords)

    from pytools import product
    nvertices = product(shape)

    vertex_indices = np.arange(nvertices).reshape(*shape)

    vertices = np.empty((dim, ) + shape, dtype=coord_dtype)
    for idim in range(dim):
        vshape = (shape[idim], ) + (1, ) * (dim - 1 - idim)
        vertices[idim] = axis_coords[idim].reshape(*vshape)

    vertices = vertices.reshape(dim, -1)

    from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup
    if group_cls is None:
        group_cls = SimplexElementGroup

    if issubclass(group_cls, SimplexElementGroup):
        is_tp = False
    elif issubclass(group_cls, TensorProductElementGroup):
        is_tp = True
    else:
        raise ValueError(f"unsupported value for 'group_cls': {group_cls}")

    el_vertices = []

    if dim == 1:
        if mesh_type is not None:
            raise ValueError(f"unsupported mesh type: '{mesh_type}'")

        for i in range(shape[0] - 1):
            # a--b

            a = vertex_indices[i]
            b = vertex_indices[i + 1]

            el_vertices.append((
                a,
                b,
            ))

    elif dim == 2:
        if mesh_type == "X" and not is_tp:
            shape_m1 = tuple(si - 1 for si in shape)

            nmidpoints = product(shape_m1)
            midpoint_indices = (
                nvertices +
                np.arange(nmidpoints).reshape(*shape_m1, order="F"))

            midpoints = np.empty((dim, ) + shape_m1, dtype=coord_dtype)
            for idim in range(dim):
                vshape = (shape_m1[idim], ) + (1, ) * idim
                left_axis_coords = axis_coords[idim][:-1]
                right_axis_coords = axis_coords[idim][1:]
                midpoints[idim] = (
                    0.5 *
                    (left_axis_coords + right_axis_coords)).reshape(*vshape)

            midpoints = midpoints.reshape(dim, -1)
            vertices = np.concatenate((vertices, midpoints), axis=1)

        elif mesh_type is None:
            pass

        else:
            raise ValueError(f"unsupported mesh type: '{mesh_type}'")

        for i in range(shape[0] - 1):
            for j in range(shape[1] - 1):

                # c--d
                # |  |
                # a--b

                a = vertex_indices[i, j]
                b = vertex_indices[i + 1, j]
                c = vertex_indices[i, j + 1]
                d = vertex_indices[i + 1, j + 1]

                if is_tp:
                    el_vertices.append((a, b, c, d))

                elif mesh_type == "X":
                    m = midpoint_indices[i, j]
                    el_vertices.append((a, b, m))
                    el_vertices.append((b, d, m))
                    el_vertices.append((d, c, m))
                    el_vertices.append((c, a, m))

                else:
                    el_vertices.append((a, b, c))
                    el_vertices.append((d, c, b))

    elif dim == 3:
        if mesh_type is not None:
            raise ValueError("unsupported mesh_type")

        for i in range(shape[0] - 1):
            for j in range(shape[1] - 1):
                for k in range(shape[2] - 1):

                    a000 = vertex_indices[i, j, k]
                    a001 = vertex_indices[i, j, k + 1]
                    a010 = vertex_indices[i, j + 1, k]
                    a011 = vertex_indices[i, j + 1, k + 1]

                    a100 = vertex_indices[i + 1, j, k]
                    a101 = vertex_indices[i + 1, j, k + 1]
                    a110 = vertex_indices[i + 1, j + 1, k]
                    a111 = vertex_indices[i + 1, j + 1, k + 1]

                    if is_tp:
                        el_vertices.append(
                            (a000, a100, a010, a110, a001, a101, a011, a111))

                    else:
                        el_vertices.append((a000, a100, a010, a001))
                        el_vertices.append((a101, a100, a001, a010))
                        el_vertices.append((a101, a011, a010, a001))

                        el_vertices.append((a100, a010, a101, a110))
                        el_vertices.append((a011, a010, a110, a101))
                        el_vertices.append((a011, a111, a101, a110))

    else:
        raise NotImplementedError("box meshes of dimension %d" % dim)

    el_vertices = np.array(el_vertices, dtype=np.int32)

    grp = make_group_from_vertices(vertices.reshape(dim, -1),
                                   el_vertices,
                                   order,
                                   group_cls=group_cls)

    # {{{ compute facial adjacency for mesh if there is tag information

    facial_adjacency_groups = None
    face_vertex_indices_to_tags = {}
    boundary_tags = list(boundary_tag_to_face.keys())
    axes = ["x", "y", "z", "w"]

    if boundary_tags:
        vert_index_to_tuple = {
            vertex_indices[itup]: itup
            for itup in np.ndindex(shape)
        }

    for tag_idx, tag in enumerate(boundary_tags):
        # Need to map the correct face vertices to the boundary tags
        for face in boundary_tag_to_face[tag]:
            if len(face) != 2:
                raise ValueError("face identifier '%s' does not "
                                 "consist of exactly two characters" % face)

            side, axis = face
            try:
                axis = axes.index(axis)
            except ValueError:
                raise ValueError("unrecognized axis in face identifier '%s'" %
                                 face)
            if axis >= dim:
                raise ValueError(
                    "axis in face identifier '%s' does not exist in %dD" %
                    (face, dim))

            if side == "-":
                vert_crit = 0
            elif side == "+":
                vert_crit = shape[axis] - 1
            else:
                raise ValueError(
                    "first character of face identifier '%s' is not"
                    "'+' or '-'" % face)

            for ielem in range(0, grp.nelements):
                for ref_fvi in grp.face_vertex_indices():
                    fvi = grp.vertex_indices[ielem, ref_fvi]
                    try:
                        fvi_tuples = [vert_index_to_tuple[i] for i in fvi]
                    except KeyError:
                        # Happens for interior faces of "X" meshes because
                        # midpoints aren't in vert_index_to_tuple. We don't
                        # care about them.
                        continue

                    if all(fvi_tuple[axis] == vert_crit
                           for fvi_tuple in fvi_tuples):
                        key = frozenset(fvi)
                        face_vertex_indices_to_tags.setdefault(key,
                                                               []).append(tag)

    if boundary_tags:
        from meshmode.mesh import (_compute_facial_adjacency_from_vertices,
                                   BTAG_ALL, BTAG_REALLY_ALL)
        boundary_tags.extend([BTAG_ALL, BTAG_REALLY_ALL])
        facial_adjacency_groups = _compute_facial_adjacency_from_vertices(
            [grp], boundary_tags, np.int32, np.int8,
            face_vertex_indices_to_tags)
    else:
        facial_adjacency_groups = None

    # }}}

    from meshmode.mesh import Mesh
    return Mesh(vertices, [grp],
                facial_adjacency_groups=facial_adjacency_groups,
                is_conforming=True,
                boundary_tags=boundary_tags)
Пример #3
0
    def get_mesh(self):
        el_type_hist = {}
        for el_type in self.element_types:
            el_type_hist[el_type] = el_type_hist.get(el_type, 0) + 1

        if not el_type_hist:
            raise RuntimeError("empty mesh in gmsh input")

        groups = self.groups = []
        ambient_dim = self.points.shape[-1]

        mesh_bulk_dim = max(el_type.dimensions for el_type in el_type_hist)

        # {{{ build vertex numbering

        # map set of face vertex indices to list of tags associated to face
        face_vertex_indices_to_tags = {}
        vertex_gmsh_index_to_mine = {}
        for element, el_vertices in enumerate(self.element_vertices):
            for gmsh_vertex_nr in el_vertices:
                if gmsh_vertex_nr not in vertex_gmsh_index_to_mine:
                    vertex_gmsh_index_to_mine[gmsh_vertex_nr] = \
                            len(vertex_gmsh_index_to_mine)
            if self.tags:
                el_tag_indexes = [self.gmsh_tag_index_to_mine[t] for t in
                                  self.element_markers[element]]
                # record tags of boundary dimension
                el_tags = [self.tags[i][0] for i in el_tag_indexes if
                           self.tags[i][1] == mesh_bulk_dim - 1]
                el_grp_verts = {vertex_gmsh_index_to_mine[e] for e in el_vertices}
                face_vertex_indices = frozenset(el_grp_verts)
                if face_vertex_indices not in face_vertex_indices_to_tags:
                    face_vertex_indices_to_tags[face_vertex_indices] = []
                face_vertex_indices_to_tags[face_vertex_indices] += el_tags

        # }}}

        # {{{ build vertex array

        gmsh_vertex_indices, my_vertex_indices = \
                list(zip(*vertex_gmsh_index_to_mine.items()))
        vertices = np.empty(
                (ambient_dim, len(vertex_gmsh_index_to_mine)), dtype=np.float64)
        vertices[:, np.array(my_vertex_indices, np.intp)] = \
                self.points[np.array(gmsh_vertex_indices, np.intp)].T

        # }}}

        from meshmode.mesh import (Mesh,
                SimplexElementGroup, TensorProductElementGroup)

        bulk_el_types = set()

        for group_el_type, ngroup_elements in el_type_hist.items():
            if group_el_type.dimensions != mesh_bulk_dim:
                continue

            bulk_el_types.add(group_el_type)

            nodes = np.empty(
                    (ambient_dim, ngroup_elements, group_el_type.node_count()),
                    np.float64)
            el_vertex_count = group_el_type.vertex_count()
            vertex_indices = np.empty(
                    (ngroup_elements, el_vertex_count),
                    np.int32)
            i = 0

            for el_vertices, el_nodes, el_type in zip(
                    self.element_vertices, self.element_nodes, self.element_types):
                if el_type is not group_el_type:
                    continue

                nodes[:, i] = self.points[el_nodes].T
                vertex_indices[i] = [
                        vertex_gmsh_index_to_mine[v_nr] for v_nr in el_vertices
                        ]

                i += 1

            import modepy as mp
            if isinstance(group_el_type, GmshSimplexElementBase):
                shape = mp.Simplex(group_el_type.dimensions)
            elif isinstance(group_el_type, GmshTensorProductElementBase):
                shape = mp.Hypercube(group_el_type.dimensions)
            else:
                raise NotImplementedError(
                        f"gmsh element type: {type(group_el_type).__name__}")

            space = mp.space_for_shape(shape, group_el_type.order)
            unit_nodes = mp.equispaced_nodes_for_space(space, shape)

            if isinstance(group_el_type, GmshSimplexElementBase):
                group = SimplexElementGroup(
                    group_el_type.order,
                    vertex_indices,
                    nodes,
                    unit_nodes=unit_nodes
                    )

                if group.dim == 2:
                    from meshmode.mesh.processing import flip_simplex_element_group
                    group = flip_simplex_element_group(vertices, group,
                            np.ones(ngroup_elements, bool))

            elif isinstance(group_el_type, GmshTensorProductElementBase):
                vertex_shuffle = type(group_el_type)(
                        order=1).get_lexicographic_gmsh_node_indices()

                group = TensorProductElementGroup(
                    group_el_type.order,
                    vertex_indices[:, vertex_shuffle],
                    nodes,
                    unit_nodes=unit_nodes
                    )
            else:
                # NOTE: already checked above
                raise AssertionError()

            groups.append(group)

        # FIXME: This is heuristic.
        if len(bulk_el_types) == 1:
            is_conforming = True
        else:
            is_conforming = mesh_bulk_dim < 3

        # construct boundary tags for mesh
        from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL
        boundary_tags = [BTAG_ALL, BTAG_REALLY_ALL]
        if self.tags:
            boundary_tags += [tag for tag, dim in self.tags if
                              dim == mesh_bulk_dim-1]

        # compute facial adjacency for Mesh if there is tag information
        facial_adjacency_groups = None
        if is_conforming and self.tags:
            from meshmode.mesh import _compute_facial_adjacency_from_vertices
            facial_adjacency_groups = _compute_facial_adjacency_from_vertices(
                    groups, boundary_tags, np.int32, np.int8,
                    face_vertex_indices_to_tags)

        return Mesh(
                vertices, groups,
                is_conforming=is_conforming,
                facial_adjacency_groups=facial_adjacency_groups,
                boundary_tags=boundary_tags,
                **self.mesh_construction_kwargs)
Пример #4
0
    def get_mesh(self):
        el_type_hist = {}
        for el_type in self.element_types:
            el_type_hist[el_type] = el_type_hist.get(el_type, 0) + 1

        if not el_type_hist:
            raise RuntimeError("empty mesh in gmsh input")

        groups = self.groups = []

        ambient_dim = self.points.shape[-1]

        mesh_bulk_dim = max(
                el_type.dimensions for el_type in six.iterkeys(el_type_hist))

        # {{{ build vertex numbering

        # map set of face vertex indices to list of tags associated to face
        face_vertex_indices_to_tags = {}
        vertex_gmsh_index_to_mine = {}
        for element, (el_vertices, el_type) in enumerate(zip(
                self.element_vertices, self.element_types)):
            for gmsh_vertex_nr in el_vertices:
                if gmsh_vertex_nr not in vertex_gmsh_index_to_mine:
                    vertex_gmsh_index_to_mine[gmsh_vertex_nr] = \
                            len(vertex_gmsh_index_to_mine)
            if self.tags:
                el_tag_indexes = [self.gmsh_tag_index_to_mine[t] for t in
                                  self.element_markers[element]]
                # record tags of boundary dimension
                el_tags = [self.tags[i][0] for i in el_tag_indexes if
                           self.tags[i][1] == mesh_bulk_dim - 1]
                el_grp_verts = {vertex_gmsh_index_to_mine[e] for e in el_vertices}
                face_vertex_indices = frozenset(el_grp_verts)
                if face_vertex_indices not in face_vertex_indices_to_tags:
                    face_vertex_indices_to_tags[face_vertex_indices] = []
                face_vertex_indices_to_tags[face_vertex_indices] += el_tags

        # }}}

        # {{{ build vertex array

        gmsh_vertex_indices, my_vertex_indices = \
                list(zip(*six.iteritems(vertex_gmsh_index_to_mine)))
        vertices = np.empty(
                (ambient_dim, len(vertex_gmsh_index_to_mine)), dtype=np.float64)
        vertices[:, np.array(my_vertex_indices, np.intp)] = \
                self.points[np.array(gmsh_vertex_indices, np.intp)].T

        # }}}

        from meshmode.mesh import (Mesh,
                SimplexElementGroup, TensorProductElementGroup)

        bulk_el_types = set()

        for group_el_type, ngroup_elements in six.iteritems(el_type_hist):
            if group_el_type.dimensions != mesh_bulk_dim:
                continue

            bulk_el_types.add(group_el_type)

            nodes = np.empty((ambient_dim, ngroup_elements, el_type.node_count()),
                    np.float64)
            el_vertex_count = group_el_type.vertex_count()
            vertex_indices = np.empty(
                    (ngroup_elements, el_vertex_count),
                    np.int32)
            i = 0

            for element, (el_vertices, el_nodes, el_type) in enumerate(zip(
                    self.element_vertices, self.element_nodes, self.element_types)):
                if el_type is not group_el_type:
                    continue

                nodes[:, i] = self.points[el_nodes].T
                vertex_indices[i] = [vertex_gmsh_index_to_mine[v_nr]
                        for v_nr in el_vertices]

                i += 1

            unit_nodes = (np.array(group_el_type.lexicographic_node_tuples(),
                    dtype=np.float64).T/group_el_type.order)*2 - 1

            if isinstance(group_el_type, GmshSimplexElementBase):
                group = SimplexElementGroup(
                    group_el_type.order,
                    vertex_indices,
                    nodes,
                    unit_nodes=unit_nodes
                    )

                if group.dim == 2:
                    from meshmode.mesh.processing import flip_simplex_element_group
                    group = flip_simplex_element_group(vertices, group,
                            np.ones(ngroup_elements, np.bool))

            elif isinstance(group_el_type, GmshTensorProductElementBase):
                gmsh_vertex_tuples = type(group_el_type)(order=1).gmsh_node_tuples()
                gmsh_vertex_tuples_loc_dict = dict(
                        (gvt, i)
                        for i, gvt in enumerate(gmsh_vertex_tuples))

                from pytools import (
                        generate_nonnegative_integer_tuples_below as gnitb)
                vertex_shuffle = np.array([
                    gmsh_vertex_tuples_loc_dict[vt]
                    for vt in gnitb(2, group_el_type.dimensions)])

                group = TensorProductElementGroup(
                    group_el_type.order,
                    vertex_indices[:, vertex_shuffle],
                    nodes,
                    unit_nodes=unit_nodes
                    )
            else:
                raise NotImplementedError("gmsh element type: %s"
                        % type(group_el_type).__name__)

            groups.append(group)

        # FIXME: This is heuristic.
        if len(bulk_el_types) == 1:
            is_conforming = True
        else:
            is_conforming = mesh_bulk_dim < 3

        # construct boundary tags for mesh
        from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL
        boundary_tags = [BTAG_ALL, BTAG_REALLY_ALL]
        if self.tags:
            boundary_tags += [tag for tag, dim in self.tags if
                              dim == mesh_bulk_dim-1]
        boundary_tags = tuple(boundary_tags)

        # compute facial adjacency for Mesh if there is tag information
        facial_adjacency_groups = None
        if is_conforming and self.tags:
            from meshmode.mesh import _compute_facial_adjacency_from_vertices
            facial_adjacency_groups = _compute_facial_adjacency_from_vertices(
                    groups, boundary_tags, np.int32, np.int8,
                    face_vertex_indices_to_tags)

        return Mesh(
                vertices, groups,
                is_conforming=is_conforming,
                facial_adjacency_groups=facial_adjacency_groups,
                boundary_tags=boundary_tags,
                **self.mesh_construction_kwargs)
Пример #5
0
def generate_box_mesh(axis_coords,
                      order=1,
                      coord_dtype=np.float64,
                      group_factory=None,
                      boundary_tag_to_face=None):
    """Create a semi-structured mesh.

    :param axis_coords: a tuple with a number of entries corresponding
        to the number of dimensions, with each entry a numpy array
        specifying the coordinates to be used along that axis.
    :param group_factory: One of :class:`meshmode.mesh.SimplexElementGroup`
        or :class:`meshmode.mesh.TensorProductElementGroup`.
    :param boundary_tag_to_face: an optional dictionary for tagging boundaries.
        The keys correspond to custom boundary tags, with the values giving
        a list of the faces on which they should be applied in terms of coordinate
        directions (``+x``, ``-x``, ``+y``, ``-y``, ``+z``, ``-z``, ``+w``, ``-w``).

        For example::

            boundary_tag_to_face={"bdry_1": ["+x", "+y"], "bdry_2": ["-x"]}

    .. versionchanged:: 2017.1

        *group_factory* parameter added.

    .. versionchanged:: 2020.1

        *boundary_tag_to_face* parameter added.
    """

    if boundary_tag_to_face is None:
        boundary_tag_to_face = {}

    for iaxis, axc in enumerate(axis_coords):
        if len(axc) < 2:
            raise ValueError("need at least two points along axis %d" %
                             (iaxis + 1))

    dim = len(axis_coords)

    shape = tuple(len(axc) for axc in axis_coords)

    from pytools import product
    nvertices = product(shape)

    vertex_indices = np.arange(nvertices).reshape(*shape, order="F")

    vertices = np.empty((dim, ) + shape, dtype=coord_dtype)
    for idim in range(dim):
        vshape = (shape[idim], ) + (1, ) * idim
        vertices[idim] = axis_coords[idim].reshape(*vshape)

    vertices = vertices.reshape(dim, -1)

    from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup
    if group_factory is None:
        group_factory = SimplexElementGroup

    if issubclass(group_factory, SimplexElementGroup):
        is_tp = False
    elif issubclass(group_factory, TensorProductElementGroup):
        is_tp = True
    else:
        raise ValueError("unsupported value for 'group_factory': %s" %
                         group_factory)

    el_vertices = []

    if dim == 1:
        for i in range(shape[0] - 1):
            # a--b

            a = vertex_indices[i]
            b = vertex_indices[i + 1]

            el_vertices.append((
                a,
                b,
            ))

    elif dim == 2:
        for i in range(shape[0] - 1):
            for j in range(shape[1] - 1):

                # c--d
                # |  |
                # a--b

                a = vertex_indices[i, j]
                b = vertex_indices[i + 1, j]
                c = vertex_indices[i, j + 1]
                d = vertex_indices[i + 1, j + 1]

                if is_tp:
                    el_vertices.append((a, b, c, d))
                else:
                    el_vertices.append((a, b, c))
                    el_vertices.append((d, c, b))

    elif dim == 3:
        for i in range(shape[0] - 1):
            for j in range(shape[1] - 1):
                for k in range(shape[2] - 1):

                    a000 = vertex_indices[i, j, k]
                    a001 = vertex_indices[i, j, k + 1]
                    a010 = vertex_indices[i, j + 1, k]
                    a011 = vertex_indices[i, j + 1, k + 1]

                    a100 = vertex_indices[i + 1, j, k]
                    a101 = vertex_indices[i + 1, j, k + 1]
                    a110 = vertex_indices[i + 1, j + 1, k]
                    a111 = vertex_indices[i + 1, j + 1, k + 1]

                    if is_tp:
                        el_vertices.append(
                            (a000, a001, a010, a011, a100, a101, a110, a111))

                    else:
                        el_vertices.append((a000, a100, a010, a001))
                        el_vertices.append((a101, a100, a001, a010))
                        el_vertices.append((a101, a011, a010, a001))

                        el_vertices.append((a100, a010, a101, a110))
                        el_vertices.append((a011, a010, a110, a101))
                        el_vertices.append((a011, a111, a101, a110))

    else:
        raise NotImplementedError("box meshes of dimension %d" % dim)

    el_vertices = np.array(el_vertices, dtype=np.int32)

    grp = make_group_from_vertices(vertices.reshape(dim, -1),
                                   el_vertices,
                                   order,
                                   group_factory=group_factory)

    # {{{ compute facial adjacency for mesh if there is tag information

    facial_adjacency_groups = None
    face_vertex_indices_to_tags = {}
    boundary_tags = list(boundary_tag_to_face.keys())
    axes = ["x", "y", "z", "w"]

    if boundary_tags:
        vert_index_to_tuple = {
            vertex_indices[itup]: itup
            for itup in np.ndindex(shape)
        }

    for tag_idx, tag in enumerate(boundary_tags):
        # Need to map the correct face vertices to the boundary tags
        for face in boundary_tag_to_face[tag]:
            if len(face) != 2:
                raise ValueError("face identifier '%s' does not "
                                 "consist of exactly two characters" % face)

            side, axis = face
            try:
                axis = axes.index(axis)
            except ValueError:
                raise ValueError("unrecognized axis in face identifier '%s'" %
                                 face)
            if axis >= dim:
                raise ValueError(
                    "axis in face identifier '%s' does not exist in %dD" %
                    (face, dim))

            if side == "-":
                vert_crit = 0
            elif side == "+":
                vert_crit = shape[axis] - 1
            else:
                raise ValueError(
                    "first character of face identifier '%s' is not"
                    "'+' or '-'" % face)

            for ielem in range(0, grp.nelements):
                for ref_fvi in grp.face_vertex_indices():
                    fvi = grp.vertex_indices[ielem, ref_fvi]
                    fvi_tuples = [vert_index_to_tuple[i] for i in fvi]

                    if all(fvi_tuple[axis] == vert_crit
                           for fvi_tuple in fvi_tuples):
                        key = frozenset(fvi)
                        face_vertex_indices_to_tags.setdefault(key,
                                                               []).append(tag)

    if boundary_tags:
        from meshmode.mesh import (_compute_facial_adjacency_from_vertices,
                                   BTAG_ALL, BTAG_REALLY_ALL)
        boundary_tags.extend([BTAG_ALL, BTAG_REALLY_ALL])
        facial_adjacency_groups = _compute_facial_adjacency_from_vertices(
            [grp], boundary_tags, np.int32, np.int8,
            face_vertex_indices_to_tags)
    else:
        facial_adjacency_groups = None

    # }}}

    from meshmode.mesh import Mesh
    return Mesh(vertices, [grp],
                facial_adjacency_groups=facial_adjacency_groups,
                is_conforming=True,
                boundary_tags=boundary_tags)