Example #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)))
Example #2
0
def test_modal_mass_matrix_for_face(dims, shape_cls, order=3):
    vol_shape = shape_cls(dims)
    vol_space = mp.space_for_shape(vol_shape, order)
    vol_basis = mp.basis_for_space(vol_space, vol_shape)

    from modepy.matrices import modal_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_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 = modal_mass_matrix_for_face(
                face, face_quad, face_basis.functions, vol_basis.functions)
        fmm2 = modal_mass_matrix_for_face(
                face, face_quad2, face_basis.functions, vol_basis.functions)

        error = la.norm(fmm - fmm2, np.inf) / la.norm(fmm2, np.inf)
        logger.info("fmm error: %.5e", error)
        assert error < 1e-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)
Example #3
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
Example #4
0
def test_deprecated_modal_face_mass_matrix(dims, order=3):
    # FIXME DEPRECATED remove along with modal_face_mass_matrix (>=2022)
    shape = mp.Simplex(dims)
    space = mp.space_for_shape(shape, order)

    vertices = mp.unit_vertices_for_shape(shape)
    basis = mp.basis_for_space(space, shape)

    from modepy.matrices import modal_face_mass_matrix
    for face in mp.faces_for_shape(shape):
        face_vertices = vertices[:, face.volume_vertex_indices]

        fmm = modal_face_mass_matrix(
                basis.functions, order, face_vertices)
        fmm2 = modal_face_mass_matrix(
                basis.functions, order+1, face_vertices)

        error = la.norm(fmm - fmm2, np.inf) / la.norm(fmm2, np.inf)
        logger.info("fmm error: %.5e", error)
        assert error < 1e-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)
Example #5
0
def tesselate_simplex(dim):
    import modepy as mp
    shape = mp.Simplex(dim)
    space = mp.space_for_shape(shape, 2)

    node_tuples = mp.node_tuples_for_space(space)
    return node_tuples, mp.submesh_for_shape(shape, node_tuples)
Example #6
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)
Example #7
0
def test_modal_coefficients_by_projection(actx_factory, quad_group_factory):
    group_cls = SimplexElementGroup
    modal_group_factory = ModalSimplexGroupFactory
    actx = actx_factory()
    order = 10
    m_order = 5

    # Make a regular rectangle mesh
    mesh = mgen.generate_regular_rect_mesh(a=(0, 0),
                                           b=(5, 3),
                                           npoints_per_axis=(10, 6),
                                           order=order,
                                           group_cls=group_cls)

    # Make discretizations
    nodal_disc = Discretization(actx, mesh, quad_group_factory(order))
    modal_disc = Discretization(actx, mesh, modal_group_factory(m_order))

    # Make connections one using quadrature projection
    nodal_to_modal_conn_quad = NodalToModalDiscretizationConnection(
        nodal_disc, modal_disc, allow_approximate_quad=True)

    def f(x):
        return 2 * actx.np.sin(5 * x)

    x_nodal = thaw(nodal_disc.nodes()[0], actx)
    nodal_f = f(x_nodal)

    # Compute modal coefficients we expect to get
    import modepy as mp

    grp, = nodal_disc.groups
    shape = mp.Simplex(grp.dim)
    space = mp.space_for_shape(shape, order=m_order)
    basis = mp.orthonormal_basis_for_space(space, shape)
    quad = grp.quadrature_rule()

    nodal_f_data = actx.to_numpy(nodal_f[0])
    vdm = mp.vandermonde(basis.functions, quad.nodes)
    w_diag = np.diag(quad.weights)

    modal_data = []
    for _, nodal_data in enumerate(nodal_f_data):
        # Compute modal data in each element: V.T * W * nodal_data
        elem_modal_f = np.dot(vdm.T, np.dot(w_diag, nodal_data))
        modal_data.append(elem_modal_f)

    modal_data = actx.from_numpy(np.asarray(modal_data))
    modal_f_expected = DOFArray(actx, data=(modal_data, ))

    # Map nodal coefficients using the quadrature-based projection
    modal_f_computed = nodal_to_modal_conn_quad(nodal_f)

    err = flat_norm(modal_f_expected - modal_f_computed)

    assert err <= 1e-13
Example #8
0
def test_orthogonality(shape, order, ebound):
    """Test orthogonality of ONBs using cubature."""

    qspace = mp.space_for_shape(shape, 2 * order)
    cub = mp.quadrature_for_space(qspace, shape)
    basis = mp.orthonormal_basis_for_space(mp.space_for_shape(shape, order),
                                           shape)

    maxerr = 0
    for i, f in enumerate(basis.functions):
        for j, g in enumerate(basis.functions):
            if i == j:
                true_result = 1
            else:
                true_result = 0
            result = cub(lambda x: f(x) * g(x))
            err = abs(result - true_result)
            print((maxerr, err))
            maxerr = max(maxerr, err)
            if err > ebound:
                print("bad", order, i, j, err)
            assert err < ebound
Example #9
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))
Example #10
0
def test_hypercube_submesh(dims, order=3):
    shape = mp.Hypercube(dims)
    space = mp.space_for_shape(shape, order)

    node_tuples = mp.node_tuples_for_space(space)
    for i, nt in enumerate(node_tuples):
        logger.info("[%4d] nodes %s", i, nt)

    assert len(node_tuples) == (order + 1)**dims

    elements = mp.submesh_for_shape(shape, node_tuples)

    for e in elements:
        logger.info("element: %s", e)

    assert len(elements) == order**dims
Example #11
0
def test_modal_coeffs_by_projection(dim):
    shape = mp.Simplex(dim)
    space = mp.space_for_shape(shape, order=5)
    basis = mp.orthonormal_basis_for_space(space, shape)

    quad = mp.XiaoGimbutasSimplexQuadrature(10, dim)
    assert quad.exact_to >= 2 * space.order

    modal_coeffs = np.random.randn(space.space_dim)
    vdm = mp.vandermonde(basis.functions, quad.nodes)

    evaluated = vdm @ modal_coeffs

    modal_coeffs_2 = vdm.T @ (evaluated * quad.weights)

    diff = modal_coeffs - modal_coeffs_2

    assert la.norm(diff, 2) < 3e-13
Example #12
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
Example #13
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}")
Example #14
0
def test_basis_grad(dim, shape_cls, order, basis_getter):
    """Do a simplistic FD-style check on the gradients of the basis."""

    h = 1.0e-4

    shape = shape_cls(dim)
    rng = np.random.Generator(np.random.PCG64(17))
    basis = basis_getter(mp.space_for_shape(shape, order), shape)

    from pytools.convergence import EOCRecorder
    from pytools import wandering_element
    for i_bf, (bf, gradbf) in enumerate(zip(
            basis.functions,
            basis.gradients,
    )):
        eoc_rec = EOCRecorder()
        for h in [1e-2, 1e-3]:
            r = mp.random_nodes_for_shape(shape, nnodes=1000, rng=rng)

            gradbf_v = np.array(gradbf(r))
            gradbf_v_num = np.array([
                (bf(r + h * unit) - bf(r - h * unit)) / (2 * h)
                for unit_tuple in wandering_element(shape.dim)
                for unit in (np.array(unit_tuple).reshape(-1, 1), )
            ])

            ref_norm = la.norm((gradbf_v).reshape(-1), np.inf)
            err = la.norm((gradbf_v_num - gradbf_v).reshape(-1), np.inf)
            if ref_norm > 1e-13:
                err = err / ref_norm

            logger.info("error: %.5", err)
            eoc_rec.add_data_point(h, err)

        tol = 1e-8
        if eoc_rec.max_error() >= tol:
            print(eoc_rec)
        assert (eoc_rec.max_error() < tol or eoc_rec.order_estimate() >= 1.5)
Example #15
0
def _get_ref_midpoints(shape, ref_vertices):
    r"""The reference element is considered to be, e.g. for a 2-simplex::

        F
        | \
        |   \
        D----E
        |   /| \
        | /  |   \
        A----B----C

    where the midpoints are ``(B, E, D)``. The same applies to other shapes
    and higher dimensions.

    :arg ref_vertices: a :class:`list` of node index :class:`tuple`\ s
        on :math:`[0, 2]^d`.
    """

    from pytools import add_tuples
    space = mp.space_for_shape(shape, 1)
    orig_vertices = [
        add_tuples(vt, vt) for vt in mp.node_tuples_for_space(space)
    ]
    return [rv for rv in ref_vertices if rv not in orig_vertices]
Example #16
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
Example #17
0
def test_symbolic_basis(shape, order, basis_getter):
    basis = basis_getter(mp.space_for_shape(shape, order), shape)
    sym_basis = [
        mp.symbolicize_function(f, shape.dim) for f in basis.functions
    ]

    # {{{ test symbolic against direct eval

    print(75 * "#")
    print("VALUES")
    print(75 * "#")

    rng = np.random.Generator(np.random.PCG64(17))
    r = mp.random_nodes_for_shape(shape, 10000, rng=rng)

    for func, sym_func in zip(basis.functions, sym_basis):
        strmap = MyStringifyMapper()
        s = strmap(sym_func)
        for name, val in strmap.cse_name_list:
            print(f"{name} <- {val}")
        print(s)

        sym_val = MyEvaluationMapper({"r": r, "abs": abs})(sym_func)
        ref_val = func(r)

        ref_norm = la.norm(ref_val, np.inf)
        err = la.norm(sym_val - ref_val, np.inf)
        if ref_norm:
            err = err / ref_norm
        print(f"ERROR: {err}")
        print()
        assert np.allclose(sym_val, ref_val)

    # }}}

    # {{{ test gradients

    print(75 * "#")
    print("GRADIENTS")
    print(75 * "#")

    sym_grad_basis = [
        mp.symbolicize_function(f, shape.dim) for f in basis.gradients
    ]

    for grad, sym_grad in zip(basis.gradients, sym_grad_basis):
        strmap = MyStringifyMapper()
        s = strmap(sym_grad)
        for name, val in strmap.cse_name_list:
            print(f"{name} <- {val}")
        print(s)

        sym_val = MyEvaluationMapper({"r": r, "abs": abs})(sym_grad)
        ref_val = grad(r)
        if not isinstance(ref_val, tuple):
            assert not isinstance(sym_val, tuple)
            sym_val = (sym_val, )
            ref_val = (ref_val, )

        for sv_i, rv_i in zip(sym_val, ref_val):
            ref_norm = la.norm(rv_i, np.inf)
            err = la.norm(sv_i - rv_i, np.inf)
            if ref_norm:
                err = err / ref_norm
            print(f"ERROR: {err}")
            print()
            assert np.allclose(sv_i, rv_i)
Example #18
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)
Example #19
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)
Example #20
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
Example #21
0
    def matrix(self, afgrp, volgrp, dtype):
        nfaces = volgrp.mesh_el_group.nfaces
        assert afgrp.nelements == nfaces * volgrp.nelements

        matrix = np.empty((volgrp.nunit_dofs, nfaces, afgrp.nunit_dofs),
                          dtype=dtype)

        import modepy as mp
        from meshmode.discretization import ElementGroupWithBasis
        from meshmode.discretization.poly_element import \
            QuadratureSimplexElementGroup

        n = volgrp.order
        m = afgrp.order
        vol_basis = volgrp.basis_obj()
        faces = mp.faces_for_shape(volgrp.shape)

        for iface, face in enumerate(faces):
            # If the face group is defined on a higher-order
            # quadrature grid, use the underlying quadrature rule
            if isinstance(afgrp, QuadratureSimplexElementGroup):
                face_quadrature = afgrp.quadrature_rule()
                if face_quadrature.exact_to < m:
                    raise ValueError(
                        "The face quadrature rule is only exact for polynomials "
                        f"of total degree {face_quadrature.exact_to}. Please "
                        "ensure a quadrature rule is used that is at least "
                        f"exact for degree {m}.")
            else:
                # NOTE: This handles the general case where
                # volume and surface quadrature rules may have different
                # integration orders
                face_quadrature = mp.quadrature_for_space(
                    mp.space_for_shape(face, 2 * max(n, m)), face)

            # If the group has a nodal basis and is unisolvent,
            # we use the basis on the face to compute the face mass matrix
            if (isinstance(afgrp, ElementGroupWithBasis)
                    and afgrp.space.space_dim == afgrp.nunit_dofs):

                face_basis = afgrp.basis_obj()

                # Sanity check for face quadrature accuracy. Not integrating
                # degree N + M polynomials here is asking for a bad time.
                if face_quadrature.exact_to < m + n:
                    raise ValueError(
                        "The face quadrature rule is only exact for polynomials "
                        f"of total degree {face_quadrature.exact_to}. Please "
                        "ensure a quadrature rule is used that is at least "
                        f"exact for degree {n+m}.")

                matrix[:, iface, :] = mp.nodal_mass_matrix_for_face(
                    face,
                    face_quadrature,
                    face_basis.functions,
                    vol_basis.functions,
                    volgrp.unit_nodes,
                    afgrp.unit_nodes,
                )
            else:
                # Otherwise, we use a routine that is purely quadrature-based
                # (no need for explicit face basis functions)
                matrix[:, iface, :] = mp.nodal_quad_mass_matrix_for_face(
                    face,
                    face_quadrature,
                    vol_basis.functions,
                    volgrp.unit_nodes,
                )

        return matrix