def simple_mpi_communication_entrypoint():
    cl_ctx = cl.create_some_context()
    queue = cl.CommandQueue(cl_ctx)
    actx = PyOpenCLArrayContext(queue, force_device_scalars=True)

    from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis
    from meshmode.mesh import BTAG_ALL

    from mpi4py import MPI
    comm = MPI.COMM_WORLD
    num_parts = comm.Get_size()

    mesh_dist = MPIMeshDistributor(comm)

    if mesh_dist.is_mananger_rank():
        from meshmode.mesh.generation import generate_regular_rect_mesh
        mesh = generate_regular_rect_mesh(a=(-1,)*2,
                                          b=(1,)*2,
                                          nelements_per_axis=(2,)*2)

        part_per_element = get_partition_by_pymetis(mesh, num_parts)

        local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts)
    else:
        local_mesh = mesh_dist.receive_mesh_part()

    dcoll = DiscretizationCollection(actx, local_mesh, order=5,
            mpi_communicator=comm)

    x = thaw(dcoll.nodes(), actx)
    myfunc = actx.np.sin(np.dot(x, [2, 3]))

    from grudge.dof_desc import as_dofdesc

    dd_int = as_dofdesc("int_faces")
    dd_vol = as_dofdesc("vol")
    dd_af = as_dofdesc("all_faces")

    all_faces_func = op.project(dcoll, dd_vol, dd_af, myfunc)
    int_faces_func = op.project(dcoll, dd_vol, dd_int, myfunc)
    bdry_faces_func = op.project(dcoll, BTAG_ALL, dd_af,
                                 op.project(dcoll, dd_vol, BTAG_ALL, myfunc))

    hopefully_zero = (
        op.project(
            dcoll, "int_faces", "all_faces",
            dcoll.opposite_face_connection()(int_faces_func)
        )
        + sum(op.project(dcoll, tpair.dd, "all_faces", tpair.int)
              for tpair in op.cross_rank_trace_pairs(dcoll, myfunc))
    ) - (all_faces_func - bdry_faces_func)

    error = actx.to_numpy(flat_norm(hopefully_zero, ord=np.inf))

    print(__file__)
    with np.printoptions(threshold=100000000, suppress=True):
        logger.debug(hopefully_zero)
    logger.info("error: %.5e", error)

    assert error < 1e-14
Beispiel #2
0
def norm(dcoll: DiscretizationCollection, vec, p, dd=None) -> "DeviceScalar":
    r"""Return the vector p-norm of a function represented
    by its vector of degrees of freedom *vec*.

    :arg vec: a :class:`~meshmode.dof_array.DOFArray` or an
        :class:`~arraycontext.container.ArrayContainer` of them.
    :arg p: an integer denoting the order of the integral norm. Currently,
        only values of 2 or `numpy.inf` are supported.
    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization if not provided.
    :returns: a nonegative scalar denoting the norm.
    """
    if dd is None:
        dd = dof_desc.DD_VOLUME

    from arraycontext import get_container_context_recursively
    actx = get_container_context_recursively(vec)

    dd = dof_desc.as_dofdesc(dd)

    if p == 2:
        from grudge.op import _apply_mass_operator
        return actx.np.sqrt(
            actx.np.abs(
                nodal_sum(
                    dcoll, dd,
                    actx.np.conjugate(vec) *
                    _apply_mass_operator(dcoll, dd, dd, vec))))
    elif p == np.inf:
        return nodal_max(dcoll, dd, actx.np.abs(vec))
    else:
        raise ValueError("unsupported norm order")
Beispiel #3
0
def h_min_from_volume(dcoll: DiscretizationCollection,
                      dim=None,
                      dd=None) -> float:
    """Returns a (minimum) characteristic length based on the volume of the
    elements. This length may not be representative if the elements have very
    high aspect ratios.

    :arg dim: an integer denoting topological dimension. If *None*, the
        spatial dimension specified by
        :attr:`grudge.DiscretizationCollection.dim` is used.
    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization if not provided.
    :returns: a scalar denoting the minimum characteristic length.
    """
    from grudge.reductions import nodal_min, elementwise_sum

    if dd is None:
        dd = DD_VOLUME
    dd = as_dofdesc(dd)

    if dim is None:
        dim = dcoll.dim

    ones = dcoll.discr_from_dd(dd).zeros(dcoll._setup_actx) + 1.0
    return nodal_min(dcoll, dd, elementwise_sum(dcoll,
                                                op.mass(dcoll, dd,
                                                        ones)))**(1.0 / dim)
    def discr_from_dd(self, dd):
        dd = as_dofdesc(dd)

        discr_tag = dd.discretization_tag

        if discr_tag is DISCR_TAG_MODAL:
            return self._modal_discr(dd.domain_tag)

        if dd.is_volume():
            if discr_tag is not DISCR_TAG_BASE:
                return self._discr_tag_volume_discr(discr_tag)
            return self._volume_discr

        if discr_tag is not DISCR_TAG_BASE:
            no_quad_discr = self.discr_from_dd(DOFDesc(dd.domain_tag))

            from meshmode.discretization import Discretization
            return Discretization(
                self._setup_actx, no_quad_discr.mesh,
                self.group_factory_for_discretization_tag(discr_tag))

        assert discr_tag is DISCR_TAG_BASE

        if dd.domain_tag is FACE_RESTR_ALL:
            return self._all_faces_volume_connection().to_discr
        elif dd.domain_tag is FACE_RESTR_INTERIOR:
            return self._interior_faces_connection().to_discr
        elif dd.is_boundary_or_partition_interface():
            return self._boundary_connection(dd.domain_tag.tag).to_discr
        else:
            raise ValueError("DOF desc tag not understood: " + str(dd))
Beispiel #5
0
def norm(dcoll: DiscretizationCollection, vec, p, dd=None) -> float:
    r"""Return the vector p-norm of a function represented
    by its vector of degrees of freedom *vec*.

    :arg vec: a :class:`~meshmode.dof_array.DOFArray` or an object array of
        a :class:`~meshmode.dof_array.DOFArray`\ s,
        where the last axis of the array must have length
        matching the volume dimension.
    :arg p: an integer denoting the order of the integral norm. Currently,
        only values of 2 or `numpy.inf` are supported.
    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization if not provided.
    :returns: a nonegative scalar denoting the norm.
    """
    if dd is None:
        dd = dof_desc.DD_VOLUME

    dd = dof_desc.as_dofdesc(dd)

    if isinstance(vec, np.ndarray):
        if p == 2:
            return sum(
                norm(dcoll, vec[idx], p, dd=dd)**2
                for idx in np.ndindex(vec.shape))**0.5
        elif p == np.inf:
            return max(
                norm(dcoll, vec[idx], np.inf, dd=dd)
                for idx in np.ndindex(vec.shape))
        else:
            raise ValueError("unsupported norm order")

    return _norm(dcoll, vec, p, dd)
Beispiel #6
0
    def __init__(self, dd, *, interior, exterior):
        """
        """
        import grudge.dof_desc as dof_desc

        self.dd = dof_desc.as_dofdesc(dd)
        self.interior = interior
        self.exterior = exterior
Beispiel #7
0
def nodes(ambient_dim, dd=None):
    import grudge.dof_desc as dof_desc

    if dd is None:
        dd = dof_desc.DD_VOLUME

    dd = dof_desc.as_dofdesc(dd)

    return np.array([NodeCoordinateComponent(i, dd)
        for i in range(ambient_dim)], dtype=object)
Beispiel #8
0
def mv_normal(
    actx: ArrayContext,
    dcoll: DiscretizationCollection,
    dd,
) -> MultiVector:
    """Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`.
    This supports both volume discretizations
    (where ambient == topological dimension) and surface discretizations
    (where ambient == topological dimension + 1). In the latter case, extra
    processing ensures that the returned normal is in the local tangent space
    of the element at the point where the normal is being evaluated.

    :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization.
    :returns: a :class:`~pymbolic.geometric_algebra.MultiVector`
        containing the unit normals.
    """
    import grudge.dof_desc as dof_desc

    dd = dof_desc.as_dofdesc(dd)

    dim = dcoll.discr_from_dd(dd).dim
    ambient_dim = dcoll.ambient_dim

    if dim == ambient_dim:
        raise ValueError(
            "may only request normals on domains whose topological "
            f"dimension ({dim}) differs from "
            f"their ambient dimension ({ambient_dim})")

    if dim == ambient_dim - 1:
        return rel_mv_normal(actx, dcoll, dd=dd)

    # NOTE: In the case of (d - 2)-dimensional curves, we don't really have
    # enough information on the face to decide what an "exterior face normal"
    # is (e.g the "normal" to a 1D curve in 3D space is actually a
    # "normal plane")
    #
    # The trick done here is that we take the surface normal, move it to the
    # face and then take a cross product with the face tangent to get the
    # correct exterior face normal vector.
    assert dim == ambient_dim - 2

    from grudge.op import project
    import grudge.dof_desc as dof_desc

    volm_normal = MultiVector(
        project(
            dcoll, dof_desc.DD_VOLUME, dd,
            rel_mv_normal(actx, dcoll,
                          dd=dof_desc.DD_VOLUME).as_vector(dtype=object)))
    pder = pseudoscalar(actx, dcoll, dd=dd)

    mv = -(volm_normal ^ pder) << volm_normal.I.inv()

    return mv / actx.np.sqrt(mv.norm_squared())
Beispiel #9
0
def integral(arg, dd=None):
    import grudge.symbolic.primitives as prim
    import grudge.dof_desc as dof_desc

    if dd is None:
        dd = dof_desc.DD_VOLUME
    dd = dof_desc.as_dofdesc(dd)

    return NodalSum(dd)(arg * prim.cse(
        MassOperator(dd_in=dd, dd_out=dd)(prim.Ones(dd)), "mass_quad_weights",
        prim.cse_scope.DISCRETIZATION))
Beispiel #10
0
def test_lazy_op_divergence(op_test_data, order):
    """Test divergence operation in lazy context."""
    eager_actx, lazy_actx, get_discr = op_test_data
    discr = get_discr(order)

    from grudge.trace_pair import interior_trace_pair
    from grudge.dof_desc import as_dofdesc
    from mirgecom.operators import div_operator

    dd_vol = as_dofdesc("vol")
    dd_faces = as_dofdesc("all_faces")

    def get_flux(u_tpair):
        dd = u_tpair.dd
        dd_allfaces = dd.with_dtag("all_faces")
        normal = thaw(discr.normal(dd), u_tpair.int[0].array_context)
        flux = u_tpair.avg @ normal
        return discr.project(dd, dd_allfaces, flux)

    def op(u):
        return div_operator(discr, dd_vol, dd_faces,
                            u, get_flux(interior_trace_pair(discr, u)))

    lazy_op = lazy_actx.compile(op)

    def get_inputs(actx):
        nodes = thaw(discr.nodes(), actx)
        u = make_obj_array([actx.np.sin(np.pi*nodes[i]) for i in range(2)])
        return u,

    tol = 1e-12
    isclose = partial(
        _isclose, discr, rel_tol=tol, abs_tol=tol, return_operands=True)

    def lazy_to_eager(u):
        return thaw(freeze(u, lazy_actx), eager_actx)

    eager_result = op(*get_inputs(eager_actx))
    lazy_result = lazy_to_eager(lazy_op(*get_inputs(lazy_actx)))
    is_close, lhs, rhs = isclose(lazy_result, eager_result)
    assert is_close, f"{lhs} not <= {rhs}"
Beispiel #11
0
def _apply_elementwise_reduction(op_name: str, dcoll: DiscretizationCollection,
                                 *args) -> DOFArray:
    r"""Returns a vector of DOFs with all entries on each element set
    to the reduction operation *op_name* over all degrees of freedom.

    :arg \*args: Arguments for the reduction operator, such as *dd* and *vec*.
    :returns: a :class:`~meshmode.dof_array.DOFArray` or object arrary of
        :class:`~meshmode.dof_array.DOFArray`s.
    """
    if len(args) == 1:
        vec, = args
        dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE)
    elif len(args) == 2:
        dd, vec = args
    else:
        raise TypeError("invalid number of arguments")

    dd = dof_desc.as_dofdesc(dd)

    if isinstance(vec, np.ndarray):
        return obj_array_vectorize(
            lambda vi: _apply_elementwise_reduction(op_name, dcoll, dd, vi),
            vec)

    actx = vec.array_context

    @memoize_in(actx,
                (_apply_elementwise_reduction, "elementwise_%s_prg" % op_name))
    def elementwise_prg():
        # FIXME: This computes the reduction value redundantly for each
        # output DOF.
        t_unit = make_loopy_program([
            "{[iel]: 0 <= iel < nelements}",
            "{[idof, jdof]: 0 <= idof, jdof < ndofs}"
        ],
                                    """
                result[iel, idof] = %s(jdof, operand[iel, jdof])
            """ % op_name,
                                    name="grudge_elementwise_%s_knl" % op_name)
        import loopy as lp
        from meshmode.transform_metadata import (ConcurrentElementInameTag,
                                                 ConcurrentDOFInameTag)
        return lp.tag_inames(t_unit, {
            "iel": ConcurrentElementInameTag(),
            "idof": ConcurrentDOFInameTag()
        })

    return DOFArray(
        actx,
        data=tuple(
            actx.call_loopy(elementwise_prg(), operand=vec_i)["result"]
            for vec_i in vec))
Beispiel #12
0
def project(dcoll, src, tgt, vec):
    """Project from one discretization to another, e.g. from the
    volume to the boundary, or from the base to the an overintegrated
    quadrature discretization.

    :arg src: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one
    :arg tgt: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one
    :arg vec: a :class:`~meshmode.dof_array.DOFArray`
    """
    src = dof_desc.as_dofdesc(src)
    tgt = dof_desc.as_dofdesc(tgt)
    if src == tgt:
        return vec

    if isinstance(vec, np.ndarray):
        return obj_array_vectorize(lambda el: project(dcoll, src, tgt, el),
                                   vec)

    if isinstance(vec, Number):
        return vec

    return dcoll.connection_from_dds(src, tgt)(vec)
Beispiel #13
0
def inverse_surface_metric_derivative(actx: ArrayContext,
                                      dcoll: DiscretizationCollection,
                                      rst_axis,
                                      xyz_axis,
                                      dd=None,
                                      *,
                                      _use_geoderiv_connection=False):
    r"""Computes the inverse surface metric derivative of the physical
    coordinate enumerated by *xyz_axis* with respect to the
    reference axis *rst_axis*. These geometric terms are used in the
    transformation of physical gradients.

    This function does not cache its results.

    :arg rst_axis: an integer denoting the reference coordinate axis.
    :arg xyz_axis: an integer denoting the physical coordinate axis.
    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization.
    :arg _use_geoderiv_connection: If *True*, process returned
        :class:`~meshmode.dof_array.DOFArray`\ s through
        :meth:`~grudge.DiscretizationCollection._base_to_geoderiv_connection`.
        This should be set based on whether the code using the result of this
        function is able to make use of these arrays. (This is an internal
        argument and not intended for use outside :mod:`grudge`.)
    :returns: a :class:`~meshmode.dof_array.DOFArray` containing the
        inverse metric derivative at each nodal coordinate.
    """
    dim = dcoll.dim
    ambient_dim = dcoll.ambient_dim

    if dd is None:
        dd = DD_VOLUME
    dd = dof_desc.as_dofdesc(dd)

    if ambient_dim == dim:
        result = inverse_metric_derivative(actx,
                                           dcoll,
                                           rst_axis,
                                           xyz_axis,
                                           dd=dd)
    else:
        inv_form1 = inverse_first_fundamental_form(actx, dcoll, dd=dd)
        result = sum(
            inv_form1[rst_axis, d] *
            forward_metric_nth_derivative(actx, dcoll, xyz_axis, d, dd=dd)
            for d in range(dim))

    if _use_geoderiv_connection:
        result = dcoll._base_to_geoderiv_connection(dd)(result)

    return result
Beispiel #14
0
def project(dcoll: DiscretizationCollection, src, tgt,
            vec) -> ArrayOrContainerT:
    """Project from one discretization to another, e.g. from the
    volume to the boundary, or from the base to the an overintegrated
    quadrature discretization.

    :arg src: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
    :arg tgt: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
    :arg vec: a :class:`~meshmode.dof_array.DOFArray` or an
        :class:`~arraycontext.container.ArrayContainer` of them.
    :returns: a :class:`~meshmode.dof_array.DOFArray` or an
        :class:`~arraycontext.container.ArrayContainer` like *vec*.
    """
    src = as_dofdesc(src)
    tgt = as_dofdesc(tgt)

    if isinstance(vec, Number) or src == tgt:
        return vec

    if not isinstance(vec, DOFArray):
        return map_array_container(partial(project, dcoll, src, tgt), vec)

    return dcoll.connection_from_dds(src, tgt)(vec)
Beispiel #15
0
def cross_rank_trace_pairs(dcoll: DiscretizationCollection,
                           ary,
                           tag=None) -> list:
    r"""Get a :class:`list` of *ary* trace pairs for each partition boundary.

    For each partition boundary, the field data values in *ary* are
    communicated to/from the neighboring partition. Presumably, this
    communication is MPI (but strictly speaking, may not be, and this
    routine is agnostic to the underlying communication).

    For each face on each partition boundary, a
    :class:`TracePair` is created with the locally, and
    remotely owned partition boundary face data as the `internal`, and `external`
    components, respectively. Each of the TracePair components are structured
    like *ary*.

    :arg ary: a single :class:`~meshmode.dof_array.DOFArray`, or an object
        array of :class:`~meshmode.dof_array.DOFArray`\ s
        of arbitrary shape.
    :returns: a :class:`list` of :class:`TracePair` objects.
    """
    if isinstance(ary, np.ndarray):
        oshape = ary.shape
        comm_vec = ary.flatten()

        n, = comm_vec.shape
        result = {}
        # FIXME: Batch this communication rather than
        # doing it in sequence.
        for ivec in range(n):
            for rank_tpair in _cross_rank_trace_pairs_scalar_field(
                    dcoll, comm_vec[ivec]):
                assert isinstance(rank_tpair.dd.domain_tag,
                                  dof_desc.DTAG_BOUNDARY)
                assert isinstance(rank_tpair.dd.domain_tag.tag, BTAG_PARTITION)
                result[rank_tpair.dd.domain_tag.tag.part_nr, ivec] = rank_tpair

        return [
            TracePair(dd=dof_desc.as_dofdesc(
                dof_desc.DTAG_BOUNDARY(BTAG_PARTITION(remote_rank))),
                      interior=make_obj_array(
                          [result[remote_rank, i].int
                           for i in range(n)]).reshape(oshape),
                      exterior=make_obj_array(
                          [result[remote_rank, i].ext
                           for i in range(n)]).reshape(oshape))
            for remote_rank in connected_ranks(dcoll)
        ]
    else:
        return _cross_rank_trace_pairs_scalar_field(dcoll, ary, tag=tag)
Beispiel #16
0
def integral(dcoll: DiscretizationCollection, dd, vec) -> float:
    """Numerically integrates a function represented by a
    :class:`~meshmode.dof_array.DOFArray` of degrees of freedom.

    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
    :arg vec: a :class:`~meshmode.dof_array.DOFArray`
    :returns: a scalar denoting the evaluated integral.
    """
    from grudge.op import _apply_mass_operator

    dd = dof_desc.as_dofdesc(dd)

    ones = dcoll.discr_from_dd(dd).zeros(vec.array_context) + 1.0
    return nodal_sum(dcoll, dd,
                     vec * _apply_mass_operator(dcoll, dd, dd, ones))
Beispiel #17
0
    def finish(self):
        self.recv_req.Wait()

        actx = self.array_context
        remote_dof_array = unflatten(self.array_context, self.bdry_discr,
                                     actx.from_numpy(self.remote_data_host))

        bdry_conn = self.dcoll.distributed_boundary_swap_connection(
            dof_desc.as_dofdesc(dof_desc.DTAG_BOUNDARY(self.remote_btag)))
        swapped_remote_dof_array = bdry_conn(remote_dof_array)

        self.send_req.Wait()

        return TracePair(self.remote_btag,
                         interior=self.local_dof_array,
                         exterior=swapped_remote_dof_array)
Beispiel #18
0
def surface_normal(ambient_dim, dim=None, dd=None):
    import grudge.dof_desc as dof_desc

    dd = dof_desc.as_dofdesc(dd)
    if dim is None:
        dim = ambient_dim - 1

    # NOTE: Don't be tempted to add a sign here. As it is, it produces
    # exterior normals for positively oriented curves.

    pder = pseudoscalar(ambient_dim, dim, dd=dd) \
            / area_element(ambient_dim, dim, dd=dd)

    # Dorst Section 3.7.2
    return cse(pder << pder.I.inv(), "surface_normal",
               cse_scope.DISCRETIZATION)
Beispiel #19
0
def filter_modally(dcoll, dd, cutoff, mode_resp_func, field):
    """Stand-alone procedural interface to spectral filtering.

    For each element group in the discretization, and restriction,
    This routine generates:

    * a filter operator:
        - *cutoff* filters only modes above this mode id
        - *mode_resp_func* function returns a filter coefficient
            for a given mode
        - memoized into the array context

    * a filtered solution wherein the filter is applied to *field*.

    Parameters
    ----------
    dcoll: :class:`grudge.discretization.DiscretizationCollection`
        Grudge discretization with boundaries object
    dd: :class:`grudge.dof_desc.DOFDesc` or as accepted by
        :func:`grudge.dof_desc.as_dofdesc`
        Describe the type of DOF vector on which to operate.
    cutoff: int
        Mode below which *field* will not be filtered
    mode_resp_func:
        Modal response function returns a filter coefficient for input mode id
    field: :class:`mirgecom.fluid.ConservedVars`
        An array container containing the relevant field(s) to filter.

    Returns
    -------
    result: :class:`mirgecom.fluid.ConservedVars`
        An array container containing the filtered field(s).
    """
    if not isinstance(field, DOFArray):
        return map_array_container(
            partial(filter_modally, dcoll, dd, cutoff, mode_resp_func), field)

    actx = field.array_context
    dd = dof_desc.as_dofdesc(dd)
    dd_modal = dof_desc.DD_VOLUME_MODAL
    discr = dcoll.discr_from_dd(dd)

    modal_map = dcoll.connection_from_dds(dd, dd_modal)
    nodal_map = dcoll.connection_from_dds(dd_modal, dd)
    field = modal_map(field)
    field = apply_spectral_filter(actx, field, discr, cutoff, mode_resp_func)
    return nodal_map(field)
Beispiel #20
0
    def __init__(self, xyz_axis, dd_in=None, dd_out=None):
        import grudge.dof_desc as dof_desc
        if dd_in is None:
            dd_in = dof_desc.DD_VOLUME

        if dd_out is None:
            dd_out = dd_in.with_discr_tag(dof_desc.DISCR_TAG_BASE)
        else:
            dd_out = dof_desc.as_dofdesc(dd_out)

        if dd_out.uses_quadrature():
            raise ValueError("differentiation outputs are not on "
                             "quadrature grids")

        super().__init__(dd_in, dd_out)

        self.xyz_axis = xyz_axis
Beispiel #21
0
def elementwise_integral(dcoll: DiscretizationCollection, dd, vec) -> DOFArray:
    """Numerically integrates a function represented by a
    :class:`~meshmode.dof_array.DOFArray` of degrees of freedom in
    each element of a discretization, given by *dd*.

    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
    :arg vec: a :class:`~meshmode.dof_array.DOFArray`
    :returns: a :class:`~meshmode.dof_array.DOFArray` containing the
        elementwise integral if *vec*.
    """
    from grudge.op import _apply_mass_operator

    dd = dof_desc.as_dofdesc(dd)

    ones = dcoll.discr_from_dd(dd).zeros(vec.array_context) + 1.0
    return elementwise_sum(dcoll, dd,
                           vec * _apply_mass_operator(dcoll, dd, dd, ones))
Beispiel #22
0
def h_min_from_volume(ambient_dim, dim=None, dd=None):
    """Defines a characteristic length based on the volume of the elements.
    This length may not be representative if the elements have very high
    aspect ratios.
    """

    import grudge.symbolic.primitives as prim
    import grudge.dof_desc as dof_desc

    if dd is None:
        dd = dof_desc.DD_VOLUME
    dd = dof_desc.as_dofdesc(dd)

    if dim is None:
        dim = ambient_dim

    return NodalMin(dd_in=dd)(ElementwiseSumOperator(dd)(MassOperator(
        dd_in=dd, dd_out=dd)(prim.Ones(dd))))**(1.0 / dim)
Beispiel #23
0
def rel_mv_normal(actx: ArrayContext,
                  dcoll: DiscretizationCollection,
                  dd=None) -> MultiVector:
    r"""Computes surface normals at each nodal location as a
    :class:`~pymbolic.geometric_algebra.MultiVector` relative to the
    pseudoscalar of the discretization described by *dd*.

    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
    """

    dd = dof_desc.as_dofdesc(dd)

    # NOTE: Don't be tempted to add a sign here. As it is, it produces
    # exterior normals for positively oriented curves.

    pder = pseudoscalar(actx, dcoll, dd=dd) / area_element(actx, dcoll, dd=dd)

    # Dorst Section 3.7.2
    return pder << pder.I.inv()
Beispiel #24
0
def _strong_scalar_grad(dcoll, dd_in, vec):
    assert dd_in == dof_desc.as_dofdesc(dof_desc.DD_VOLUME)

    from grudge.geometry import inverse_surface_metric_derivative_mat

    discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME)
    actx = vec.array_context

    inverse_jac_mat = inverse_surface_metric_derivative_mat(
        actx,
        dcoll,
        _use_geoderiv_connection=actx.supports_nonscalar_broadcasting)
    return _gradient_kernel(actx,
                            discr,
                            discr,
                            _reference_derivative_matrices,
                            inverse_jac_mat,
                            vec,
                            metric_in_matvec=False)
Beispiel #25
0
def norm(dcoll, vec, p, dd=None):
    if dd is None:
        dd = "vol"

    dd = dof_desc.as_dofdesc(dd)

    if isinstance(vec, np.ndarray):
        if p == 2:
            return sum(
                norm(dcoll, vec[idx], p, dd=dd)**2
                for idx in np.ndindex(vec.shape))**0.5
        elif p == np.inf:
            return max(
                norm(dcoll, vec[idx], np.inf, dd=dd)
                for idx in np.ndindex(vec.shape))
        else:
            raise ValueError("unsupported norm order")

    return _norm(dcoll, p, dd)(arg=vec)
Beispiel #26
0
def elementwise_integral(dcoll: DiscretizationCollection,
                         *args) -> ArrayOrContainerT:
    """Numerically integrates a function represented by a
    :class:`~meshmode.dof_array.DOFArray` of degrees of freedom in
    each element of a discretization, given by *dd*.

    May be called with ``(vec)`` or ``(dd, vec)``.

    The input *vec* can either be a :class:`~meshmode.dof_array.DOFArray` or
    an :class:`~arraycontext.container.ArrayContainer` with
    :class:`~meshmode.dof_array.DOFArray` entries. If the underlying
    array context (see :class:`arraycontext.ArrayContext`) for *vec*
    supports nonscalar broadcasting, all :class:`~meshmode.dof_array.DOFArray`
    entries will contain a single value for each element. Otherwise, the
    entries will have the same number of degrees of freedom as *vec*, but
    set to the same value.

    :arg dcoll: a :class:`grudge.discretization.DiscretizationCollection`.
    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization if not provided.
    :arg vec: a :class:`~meshmode.dof_array.DOFArray` or an
        :class:`~arraycontext.container.ArrayContainer` of them.
    :returns: a :class:`~meshmode.dof_array.DOFArray` or an
        :class:`~arraycontext.container.ArrayContainer` like *vec* containing the
        elementwise integral if *vec*.
    """
    if len(args) == 1:
        vec, = args
        dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE)
    elif len(args) == 2:
        dd, vec = args
    else:
        raise TypeError("invalid number of arguments")

    dd = dof_desc.as_dofdesc(dd)

    from grudge.op import _apply_mass_operator

    ones = dcoll.discr_from_dd(dd).zeros(vec.array_context) + 1.0
    return elementwise_sum(dcoll, dd,
                           vec * _apply_mass_operator(dcoll, dd, dd, ones))
Beispiel #27
0
def mv_normal(dd, ambient_dim, dim=None):
    """Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`."""
    import grudge.dof_desc as dof_desc

    dd = dof_desc.as_dofdesc(dd)
    if not dd.is_trace():
        raise ValueError("may only request normals on boundaries")

    if dim is None:
        dim = ambient_dim - 1

    if dim == ambient_dim - 1:
        return surface_normal(ambient_dim, dim=dim, dd=dd)

    # NOTE: In the case of (d - 2)-dimensional curves, we don't really have
    # enough information on the face to decide what an "exterior face normal"
    # is (e.g the "normal" to a 1D curve in 3D space is actually a
    # "normal plane")
    #
    # The trick done here is that we take the surface normal, move it to the
    # face and then take a cross product with the face tangent to get the
    # correct exterior face normal vector.
    assert dim == ambient_dim - 2

    from grudge.symbolic.operators import project
    import grudge.dof_desc as dof_desc

    volm_normal = (
            surface_normal(ambient_dim, dim=dim + 1,
                           dd=dof_desc.DD_VOLUME)
            .map(project(dof_desc.DD_VOLUME, dd)))
    pder = pseudoscalar(ambient_dim, dim, dd=dd)

    mv = cse(-(volm_normal ^ pder) << volm_normal.I.inv(),
            "face_normal",
            cse_scope.DISCRETIZATION)

    return cse(mv / sqrt(mv.norm_squared()),
            "unit_face_normal",
            cse_scope.DISCRETIZATION)
Beispiel #28
0
    def finish(self):
        # Wait for the nonblocking receive request to complete before
        # accessing the data
        self.recv_req.Wait()

        # Nonblocking receive is complete, we can now access the data and apply
        # the boundary-swap connection
        actx = self.array_context
        remote_bdry_data_flat = from_numpy(self.remote_data_host_numpy, actx)
        remote_bdry_data = unflatten(self.local_bdry_data,
                                     remote_bdry_data_flat, actx)
        bdry_conn = self.dcoll.distributed_boundary_swap_connection(
            dof_desc.as_dofdesc(dof_desc.DTAG_BOUNDARY(self.remote_btag)))
        swapped_remote_bdry_data = bdry_conn(remote_bdry_data)

        # Complete the nonblocking send request associated with communicating
        # `self.local_bdry_data_np`
        self.send_req.Wait()

        return TracePair(self.remote_btag,
                         interior=self.local_bdry_data,
                         exterior=swapped_remote_bdry_data)
Beispiel #29
0
def norm(p, arg, dd=None):
    """
    :arg arg: is assumed to be a vector, i.e. have shape ``(n,)``.
    """
    import grudge.symbolic.primitives as prim
    import grudge.dof_desc as dof_desc

    if dd is None:
        dd = dof_desc.DD_VOLUME
    dd = dof_desc.as_dofdesc(dd)

    if p == 2:
        norm_squared = NodalSum(dd_in=dd)(arg * MassOperator()(arg))

        if isinstance(norm_squared, np.ndarray):
            if len(norm_squared.shape) != 1:
                raise NotImplementedError("can only take the norm of vectors")

            norm_squared = norm_squared.sum()

        return prim.sqrt(norm_squared)

    elif p == np.inf:
        result = NodalMax(dd_in=dd)(prim.fabs(arg))

        if isinstance(result, np.ndarray):
            if len(result.shape) != 1:
                raise NotImplementedError("can only take the norm of vectors")

            from pymbolic.primitives import Max
            result = Max(result)

        return result

    else:
        raise ValueError("unsupported value of p")
Beispiel #30
0
 def __init__(self, dd, *, interior, exterior):
     object.__setattr__(self, "dd", dof_desc.as_dofdesc(dd))
     object.__setattr__(self, "interior", interior)
     object.__setattr__(self, "exterior", exterior)