示例#1
0
def test_deprecated_nodal_face_mass_matrix(dims, order=3):
    # FIXME DEPRECATED remove along with nodal_face_mass_matrix (>=2022)
    vol_shape = mp.Simplex(dims)
    vol_space = mp.space_for_shape(vol_shape, order)

    vertices = mp.unit_vertices_for_shape(vol_shape)
    volume_nodes = mp.edge_clustered_nodes_for_space(vol_space, vol_shape)
    volume_basis = mp.basis_for_space(vol_space, vol_shape)

    from modepy.matrices import nodal_face_mass_matrix
    for face in mp.faces_for_shape(vol_shape):
        face_space = mp.space_for_shape(face, order)
        face_nodes = mp.edge_clustered_nodes_for_space(face_space, face)
        face_vertices = vertices[:, face.volume_vertex_indices]

        fmm = nodal_face_mass_matrix(
                volume_basis.functions, volume_nodes,
                face_nodes, order, face_vertices)
        fmm2 = nodal_face_mass_matrix(
                volume_basis.functions,
                volume_nodes, face_nodes, order+1, face_vertices)

        error = la.norm(fmm - fmm2, np.inf) / la.norm(fmm2, np.inf)
        logger.info("fmm error: %.5e", error)
        assert error < 5e-11, f"error {error:.5e} on face {face.face_index}"

        fmm[np.abs(fmm) < 1e-13] = 0
        nnz = np.sum(fmm > 0)

        logger.info("fmm: nnz %d\n%s", nnz, fmm)

        logger.info("mass matrix:\n%s", mp.mass_matrix(
            mp.basis_for_space(face_space, face).functions,
            mp.edge_clustered_nodes_for_space(face_space, face)))
示例#2
0
def test_resampling_matrix(dims, shape_cls, ncoarse=5, nfine=10):
    shape = shape_cls(dims)

    coarse_space = mp.space_for_shape(shape, ncoarse)
    fine_space = mp.space_for_shape(shape, nfine)

    coarse_nodes = mp.edge_clustered_nodes_for_space(coarse_space, shape)
    coarse_basis = mp.basis_for_space(coarse_space, shape)

    fine_nodes = mp.edge_clustered_nodes_for_space(fine_space, shape)
    fine_basis = mp.basis_for_space(fine_space, shape)

    my_eye = np.dot(
            mp.resampling_matrix(fine_basis.functions, coarse_nodes, fine_nodes),
            mp.resampling_matrix(coarse_basis.functions, fine_nodes, coarse_nodes))

    assert la.norm(my_eye - np.eye(len(my_eye))) < 3e-13

    my_eye_least_squares = np.dot(
            mp.resampling_matrix(coarse_basis.functions, coarse_nodes, fine_nodes,
                least_squares_ok=True),
            mp.resampling_matrix(coarse_basis.functions, fine_nodes, coarse_nodes),
            )

    assert la.norm(my_eye_least_squares - np.eye(len(my_eye_least_squares))) < 4e-13
示例#3
0
    def __init__(self, order, vertex_indices, nodes,
            element_nr_base=None, node_nr_base=None,
            unit_nodes=None, dim=None, **kwargs):
        """
        :arg order: the maximum total degree used for interpolation.
        :arg nodes: ``(ambient_dim, nelements, nunit_nodes)``
            The nodes are assumed to be mapped versions of *unit_nodes*.
        :arg unit_nodes: ``(dim, nunit_nodes)`` The unit nodes of which
            *nodes* is a mapped version. If unspecified, the nodes from
            :func:`modepy.edge_clustered_nodes_for_space` are assumed.
            These must be in unit coordinates as defined in :mod:`modepy`.
        :arg dim: only used if *unit_nodes* is *None*, to get
            the default unit nodes.

        Do not supply *element_nr_base* and *node_nr_base*, they will be
        automatically assigned.
        """

        if unit_nodes is not None:
            _dim = unit_nodes.shape[0]
            if dim is not None and _dim != dim:
                raise ValueError("'dim' does not match 'unit_nodes' dimension")
            else:
                dim = _dim
        else:
            if dim is None:
                raise TypeError("'dim' must be passed if 'unit_nodes' is not passed")

        # dim is now usable
        shape = self._modepy_shape_cls(dim)
        space = mp.space_for_shape(shape, order)

        if unit_nodes is None:
            unit_nodes = mp.edge_clustered_nodes_for_space(space, shape)

        if nodes is not None:
            if unit_nodes.shape[-1] != nodes.shape[-1]:
                raise ValueError(
                        "'nodes' has wrong number of unit nodes per element."
                        f" expected {unit_nodes.shape[-1]}, "
                        f" but got {nodes.shape[-1]}.")

        if vertex_indices is not None:
            if not issubclass(vertex_indices.dtype.type, np.integer):
                raise TypeError("'vertex_indices' must be integral")

            if vertex_indices.shape[-1] != shape.nvertices:
                raise ValueError(
                        "'vertex_indices' has wrong number of vertices per element."
                        f" expected {shape.nvertices},"
                        f" got {vertex_indices.shape[-1]}")

        super().__init__(order, vertex_indices, nodes,
                element_nr_base=element_nr_base,
                node_nr_base=node_nr_base,
                unit_nodes=unit_nodes,
                dim=dim,
                _modepy_shape=shape,
                _modepy_space=space)
示例#4
0
def test_nodal_mass_matrix_for_face(dims, shape_cls, order=3):
    vol_shape = shape_cls(dims)
    vol_space = mp.space_for_shape(vol_shape, order)

    volume_nodes = mp.edge_clustered_nodes_for_space(vol_space, vol_shape)
    volume_basis = mp.basis_for_space(vol_space, vol_shape)

    from modepy.matrices import (nodal_mass_matrix_for_face,
        nodal_quad_mass_matrix_for_face)
    for face in mp.faces_for_shape(vol_shape):
        face_space = mp.space_for_shape(face, order)
        face_basis = mp.basis_for_space(face_space, face)
        face_nodes = mp.edge_clustered_nodes_for_space(face_space, face)
        face_quad = mp.quadrature_for_space(mp.space_for_shape(face, 2*order), face)
        face_quad2 = mp.quadrature_for_space(
                mp.space_for_shape(face, 2*order+2), face)
        fmm = nodal_mass_matrix_for_face(
                face, face_quad, face_basis.functions, volume_basis.functions,
                volume_nodes, face_nodes)
        fmm2 = nodal_quad_mass_matrix_for_face(
                face, face_quad2, volume_basis.functions, volume_nodes)

        for f_face in face_basis.functions:
            fval_nodal = f_face(face_nodes)
            fval_quad = f_face(face_quad2.nodes)
            assert (
                    la.norm(fmm@fval_nodal - fmm2@fval_quad, np.inf)
                    / la.norm(fval_quad)) < 3e-15

        fmm[np.abs(fmm) < 1e-13] = 0
        nnz = np.sum(fmm > 0)

        logger.info("fmm: nnz %d\n%s", nnz, fmm)

        logger.info("mass matrix:\n%s",
                mp.mass_matrix(face_basis.functions, face_nodes))
示例#5
0
def test_diff_matrix(dims, shape_cls, order=5):
    shape = shape_cls(dims)
    space = mp.space_for_shape(shape, order)
    nodes = mp.edge_clustered_nodes_for_space(space, shape)
    basis = mp.basis_for_space(space, shape)

    diff_mat = mp.differentiation_matrices(basis.functions, basis.gradients, nodes)
    if isinstance(diff_mat, tuple):
        diff_mat = diff_mat[0]

    f = np.sin(nodes[0])

    df_dx = np.cos(nodes[0])
    df_dx_num = np.dot(diff_mat, f)

    error = la.norm(df_dx - df_dx_num) / la.norm(df_dx)
    logger.info("error: %.5e", error)
    assert error < 2.0e-4, error
示例#6
0
def test_estimate_lebesgue_constant(dims, order, shape_cls, visualize=False):
    logging.basicConfig(level=logging.INFO)
    shape = shape_cls(dims)
    space = mp.space_for_shape(shape, order)

    nodes = mp.edge_clustered_nodes_for_space(space, shape)

    from modepy.tools import estimate_lebesgue_constant
    lebesgue_constant = estimate_lebesgue_constant(order, nodes, shape=shape)
    logger.info("%s-%d/%s: %.5e", shape, dims, order, lebesgue_constant)

    if not visualize:
        return

    from modepy.tools import _evaluate_lebesgue_function
    lebesgue, equi_node_tuples, equi_nodes = \
            _evaluate_lebesgue_function(order, nodes, shape)

    import matplotlib.pyplot as plt
    fig = plt.figure()
    ax = fig.gca()
    ax.grid()

    if dims == 1:
        ax.plot(equi_nodes[0], lebesgue)
        ax.set_xlabel("$x$")
        ax.set_ylabel(fr"$\lambda_{order}$")
    elif dims == 2:
        ax.plot(nodes[0], nodes[1], "ko")
        p = ax.tricontourf(equi_nodes[0], equi_nodes[1], lebesgue, levels=16)
        fig.colorbar(p)
        ax.set_xlim([-1.1, 1.1])
        ax.set_ylim([-1.1, 1.1])
        ax.set_aspect("equal")
    else:
        raise ValueError(f"unsupported dimension: {dims}")

    shape_name = shape_cls.__name__.lower()
    fig.savefig(f"estimate_lebesgue_constant_{shape_name}_{dims}_order_{order}")
示例#7
0
def make_group_from_vertices(vertices,
                             vertex_indices,
                             order,
                             group_cls=None,
                             unit_nodes=None):
    # shape: (ambient_dim, nelements, nvertices)
    ambient_dim = vertices.shape[0]
    el_vertices = vertices[:, vertex_indices]

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

    if issubclass(group_cls, SimplexElementGroup):
        if order < 1:
            raise ValueError("can't represent simplices with mesh order < 1")

        el_origins = el_vertices[:, :, 0][:, :, np.newaxis]
        # ambient_dim, nelements, nspan_vectors
        spanning_vectors = (el_vertices[:, :, 1:] - el_origins)

        nspan_vectors = spanning_vectors.shape[-1]
        dim = nspan_vectors

        # dim, nunit_nodes
        if unit_nodes is None:
            shape = mp.Simplex(dim)
            space = mp.space_for_shape(shape, order)
            unit_nodes = mp.edge_clustered_nodes_for_space(space, shape)

        unit_nodes_01 = 0.5 + 0.5 * unit_nodes
        nodes = np.einsum("si,des->dei", unit_nodes_01,
                          spanning_vectors) + el_origins

    elif issubclass(group_cls, TensorProductElementGroup):
        nelements, nvertices = vertex_indices.shape

        dim = nvertices.bit_length() - 1
        if nvertices != 2**dim:
            raise ValueError("invalid number of vertices for tensor-product "
                             "elements, must be power of two")

        shape = mp.Hypercube(dim)
        space = mp.space_for_shape(shape, order)

        if unit_nodes is None:
            unit_nodes = mp.edge_clustered_nodes_for_space(space, shape)

        # shape: (dim, nnodes)
        unit_nodes_01 = 0.5 + 0.5 * unit_nodes
        _, nnodes = unit_nodes.shape

        vertex_tuples = mp.node_tuples_for_space(type(space)(dim, 1))
        assert len(vertex_tuples) == nvertices

        vdm = np.empty((nvertices, nvertices))
        for i, vertex_tuple in enumerate(vertex_tuples):
            for j, func_tuple in enumerate(vertex_tuples):
                vertex_ref = np.array(vertex_tuple, dtype=np.float64)
                vdm[i, j] = np.prod(vertex_ref**func_tuple)

        # shape: (ambient_dim, nelements, nvertices)
        coeffs = np.empty((ambient_dim, nelements, nvertices))
        for d in range(ambient_dim):
            coeffs[d] = la.solve(vdm, el_vertices[d].T).T

        vdm_nodes = np.zeros((nnodes, nvertices))
        for j, func_tuple in enumerate(vertex_tuples):
            vdm_nodes[:,
                      j] = np.prod(unit_nodes_01**np.array(func_tuple).reshape(
                          -1, 1),
                                   axis=0)

        nodes = np.einsum("ij,dej->dei", vdm_nodes, coeffs)
    else:
        raise ValueError(f"unsupported value for 'group_cls': {group_cls}")

    # make contiguous
    nodes = nodes.copy()

    return group_cls(order, vertex_indices, nodes, unit_nodes=unit_nodes)
示例#8
0
def test_sanity_single_element(actx_factory,
                               dim,
                               mesh_order,
                               group_cls,
                               visualize=False):
    pytest.importorskip("pytential")
    actx = actx_factory()

    if group_cls is SimplexElementGroup:
        group_factory = PolynomialWarpAndBlendGroupFactory(mesh_order + 3)
    elif group_cls is TensorProductElementGroup:
        group_factory = LegendreGaussLobattoTensorProductGroupFactory(
            mesh_order + 3)
    else:
        raise TypeError

    import modepy as mp
    shape = group_cls._modepy_shape_cls(dim)
    space = mp.space_for_shape(shape, mesh_order)

    vertices = mp.unit_vertices_for_shape(shape)
    nodes = mp.edge_clustered_nodes_for_space(space, shape).reshape(dim, 1, -1)
    vertex_indices = np.arange(shape.nvertices, dtype=np.int32).reshape(1, -1)

    center = np.empty(dim, np.float64)
    center.fill(-0.5)

    mg = group_cls(mesh_order, vertex_indices, nodes, dim=dim)
    mesh = Mesh(vertices, [mg], is_conforming=True)

    from meshmode.discretization import Discretization
    vol_discr = Discretization(actx, mesh, group_factory)

    # {{{ volume calculation check

    if isinstance(mg, SimplexElementGroup):
        from pytools import factorial
        true_vol = 1 / factorial(dim) * 2**dim
    elif isinstance(mg, TensorProductElementGroup):
        true_vol = 2**dim
    else:
        raise TypeError

    nodes = thaw(vol_discr.nodes(), actx)
    vol_one = 1 + 0 * nodes[0]

    from pytential import norm, integral  # noqa
    comp_vol = integral(vol_discr, vol_one)
    rel_vol_err = abs(true_vol - comp_vol) / true_vol

    assert rel_vol_err < 1e-12

    # }}}

    # {{{ boundary discretization

    from meshmode.discretization.connection import make_face_restriction
    bdry_connection = make_face_restriction(actx, vol_discr, group_factory,
                                            BTAG_ALL)
    bdry_discr = bdry_connection.to_discr

    # }}}

    from pytential import bind, sym
    bdry_normals = bind(bdry_discr, sym.normal(dim).as_vector())(actx)

    if visualize:
        from meshmode.discretization.visualization import make_visualizer
        bdry_vis = make_visualizer(actx, bdry_discr, 4)

        bdry_vis.write_vtk_file("sanity_single_element_boundary.vtu",
                                [("normals", bdry_normals)])

    normal_outward_check = bind(
        bdry_discr,
        sym.normal(dim)
        | (sym.nodes(dim) + 0.5 * sym.ones_vec(dim)),
    )(actx).as_scalar()

    normal_outward_check = flatten_to_numpy(actx, normal_outward_check > 0)
    assert normal_outward_check.all(), normal_outward_check
示例#9
0
def make_face_restriction(actx, discr, group_factory, boundary_tag,
        per_face_groups=False):
    """Create a mesh, a discretization and a connection to restrict
    a function on *discr* to its values on the edges of element faces
    denoted by *boundary_tag*.

    :arg boundary_tag: The boundary tag for which to create a face
        restriction. May be
        :class:`FACE_RESTR_INTERIOR`
        to indicate interior faces, or
        :class:`FACE_RESTR_ALL`
        to make a discretization consisting of all (interior and
        boundary) faces.

    :arg per_face_groups: If *True*, the resulting discretization is
        guaranteed to have groups organized as::

            (grp0, face0), (grp0, face1), ... (grp0, faceN),
            (grp1, face0), (grp1, face1), ... (grp1, faceN), ...

        each with the elements in the same order as the originating
        group. If *False*, volume and boundary groups correspond with
        each other one-to-one, and an interpolation batch is created
        per face.

    :return: a
        :class:`meshmode.discretization.connection.DirectDiscretizationConnection`
        representing the new connection. The new boundary discretization can be
        obtained from the
        :attr:`meshmode.discretization.connection.DirectDiscretizationConnection.to_discr`
        attribute of the return value, and the corresponding new boundary mesh
        from that.

    """

    if boundary_tag is None:
        boundary_tag = FACE_RESTR_INTERIOR
        from warnings import warn
        warn("passing *None* for boundary_tag is deprecated--pass "
                "FACE_RESTR_INTERIOR instead",
                DeprecationWarning, stacklevel=2)

    logger.info("building face restriction: start")

    # {{{ gather boundary vertices

    bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag)

    vol_to_bdry_vertices = np.empty(
            discr.mesh.vertices.shape[-1],
            discr.mesh.vertices.dtype)
    vol_to_bdry_vertices.fill(-1)
    vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange(
            len(bdry_vertex_vol_nrs), dtype=np.intp)

    bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs]

    # }}}

    from meshmode.mesh import Mesh, _ModepyElementGroup
    bdry_mesh_groups = []
    connection_data = {}

    if boundary_tag not in [FACE_RESTR_ALL, FACE_RESTR_INTERIOR]:
        btag_bit = discr.mesh.boundary_tag_bit(boundary_tag)

    for igrp, (grp, fagrp_map) in enumerate(
            zip(discr.groups, discr.mesh.facial_adjacency_groups)):

        mgrp = grp.mesh_el_group

        if not isinstance(mgrp, _ModepyElementGroup):
            raise NotImplementedError("can only take boundary of "
                    "meshes based on SimplexElementGroup and "
                    "TensorProductElementGroup")

        # {{{ pull together per-group face lists

        group_boundary_faces = []

        if boundary_tag is FACE_RESTR_INTERIOR:
            for fagrp in fagrp_map.values():
                if fagrp.ineighbor_group is None:
                    # boundary faces -> not looking for those
                    continue

                group_boundary_faces.extend(
                        zip(fagrp.elements, fagrp.element_faces))

        elif boundary_tag is FACE_RESTR_ALL:
            group_boundary_faces.extend(
                    (iel, iface)
                    for iface in range(grp.mesh_el_group.nfaces)
                    for iel in range(grp.nelements)
                    )

        else:
            bdry_grp = fagrp_map.get(None)
            if bdry_grp is not None:
                nb_el_bits = -bdry_grp.neighbors
                face_relevant_flags = (nb_el_bits & btag_bit) != 0

                group_boundary_faces.extend(
                            zip(
                                bdry_grp.elements[face_relevant_flags],
                                bdry_grp.element_faces[face_relevant_flags]))

        # }}}

        batch_base = 0

        # group by face_index

        for face in mgrp._modepy_faces:
            batch_boundary_el_numbers_in_grp = np.array([
                ibface_el
                for ibface_el, ibface_face in group_boundary_faces
                if ibface_face == face.face_index
                ], dtype=np.intp)

            # {{{ preallocate arrays for mesh group

            nbatch_elements = len(batch_boundary_el_numbers_in_grp)

            if per_face_groups or face.face_index == 0:
                if per_face_groups:
                    ngroup_bdry_elements = nbatch_elements
                else:
                    ngroup_bdry_elements = len(group_boundary_faces)

                # make up some not-terrible nodes for the boundary Mesh
                space = mp.space_for_shape(face, mgrp.order)
                bdry_unit_nodes = mp.edge_clustered_nodes_for_space(space, face)

                vol_basis = mp.basis_for_space(
                        mgrp._modepy_space, mgrp._modepy_shape).functions

                vertex_indices = np.empty(
                        (ngroup_bdry_elements, face.nvertices),
                        mgrp.vertex_indices.dtype)

                nbdry_unit_nodes = bdry_unit_nodes.shape[-1]
                nodes = np.empty(
                        (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes),
                        dtype=np.float64)
            # }}}

            new_el_numbers = batch_base + np.arange(nbatch_elements)
            if not per_face_groups:
                batch_base += nbatch_elements

            # {{{ no per-element axes in these computations

            face_unit_nodes = face.map_to_volume(bdry_unit_nodes)
            resampling_mat = mp.resampling_matrix(
                    vol_basis,
                    face_unit_nodes, mgrp.unit_nodes)

            # }}}

            # {{{ build information for mesh element group

            # Find vertex_indices
            glob_face_vertices = mgrp.vertex_indices[
                    batch_boundary_el_numbers_in_grp][:, face.volume_vertex_indices]
            vertex_indices[new_el_numbers] = \
                    vol_to_bdry_vertices[glob_face_vertices]

            # Find nodes
            nodes[:, new_el_numbers, :] = np.einsum(
                    "ij,dej->dei",
                    resampling_mat,
                    mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :])

            # }}}

            connection_data[igrp, face.face_index] = _ConnectionBatchData(
                    group_source_element_indices=batch_boundary_el_numbers_in_grp,
                    group_target_element_indices=new_el_numbers,
                    face=face,
                    )

            is_last_face = face.face_index + 1 == mgrp.nfaces

            if per_face_groups or is_last_face:
                bdry_mesh_group = type(mgrp)(
                        mgrp.order, vertex_indices, nodes,
                        unit_nodes=bdry_unit_nodes)
                bdry_mesh_groups.append(bdry_mesh_group)

    bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups)

    from meshmode.discretization import Discretization
    bdry_discr = Discretization(
            actx, bdry_mesh, group_factory)

    connection = _build_boundary_connection(
            actx, discr, bdry_discr, connection_data,
            per_face_groups)

    logger.info("building face restriction: done")

    return connection