Пример #1
0
def mv_normal(dd, ambient_dim, dim=None):
    """Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`."""

    dd = as_dofdesc(dd)

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

    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
    mv = pder << pder.I.inv()

    if dim == 0:
        # NOTE: when the mesh is 0D, we do not have a clear notion of
        # `exterior normal`, so we just take the tangent of the 1D element,
        # project it to the element faces and make it signed
        tangent = parametrization_derivative(ambient_dim, dim=1, dd=DD_VOLUME)
        tangent = tangent / sqrt(tangent.norm_squared())

        from grudge.symbolic.operators import project
        project = project(DD_VOLUME, dd)
        mv = MultiVector(
            np.array(
                [mv.as_scalar() * project(t) for t in tangent.as_vector()]))

    return cse(mv, "normal", cse_scope.DISCRETIZATION)
Пример #2
0
 def map_nabla(self, expr):
     from pytools.obj_array import make_obj_array
     return MultiVector(
         make_obj_array([
             prim.NablaComponent(axis, expr.nabla_id)
             for axis in range(self.ambient_dim)
         ]))
Пример #3
0
def parametrization_derivative(actx: ArrayContext,
                               dcoll: DiscretizationCollection,
                               dd) -> MultiVector:
    r"""Computes the product of forward metric derivatives spanning the
    tangent space with topological dimension *dim*.

    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization.
    :returns: a :class:`pymbolic.geometric_algebra.MultiVector` containing
        the product of metric derivatives.
    """
    if dd is None:
        dd = DD_VOLUME

    dim = dcoll.discr_from_dd(dd).dim
    if dim == 0:
        from pymbolic.geometric_algebra import get_euclidean_space

        return MultiVector(_signed_face_ones(actx, dcoll, dd),
                           space=get_euclidean_space(dcoll.ambient_dim))

    from pytools import product

    return product(
        forward_metric_derivative_mv(actx, dcoll, rst_axis, dd)
        for rst_axis in range(dim))
Пример #4
0
 def dnabla(self, ambient_dim):
     from pymbolic.geometric_algebra import MultiVector
     from pytools.obj_array import make_obj_array
     return MultiVector(
         make_obj_array([
             NablaComponent(axis, self.my_id) for axis in range(ambient_dim)
         ]))
Пример #5
0
def face_normal(face: Face, normalize=True) -> np.ndarray:
    """
    .. versionadded :: 2021.2.1
    """
    volume_vertices = unit_vertices_for_shape(face.volume_shape)
    face_vertices = volume_vertices[:, face.volume_vertex_indices]

    if face.dim == 0:
        # FIXME Grrrr. Hardcoded special case. Got a better idea?
        (fv,), = face_vertices
        return np.array([np.sign(fv)])

    # Compute the outer product of the vectors spanning the surface, obtaining
    # the surface pseudoscalar.
    from pymbolic.geometric_algebra import MultiVector
    from operator import xor as outerprod
    from functools import reduce
    surface_ps = reduce(outerprod, [
        MultiVector(face_vertices[:, i+1] - face_vertices[:, 0])
        for i in range(face.dim)])

    if normalize:
        surface_ps = surface_ps / np.sqrt(surface_ps.norm_squared())

    # Compute the normal as the dual of the surface pseudoscalar.
    return surface_ps.dual().as_vector()
Пример #6
0
    def outprod_with_unit(i, at):
        unit_vec = np.zeros(dim)
        unit_vec[i] = 1

        vecs = par_vecs[:]
        vecs[at] = MultiVector(unit_vec)

        return outerprod(vecs)
Пример #7
0
        def det(v):
            nnodes = v[0].shape[0]
            det_v = np.empty(nnodes)

            for i in range(nnodes):
                outer_product = reduce(xor, [MultiVector(x[i, :].T) for x in v])
                det_v[i] = abs((outer_product.I | outer_product).as_scalar())

            return det_v
Пример #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())
Пример #9
0
def parametrization_derivative(ambient_dim, dim=None, dd=None):
    if dim is None:
        dim = ambient_dim

    if dim == 0:
        return MultiVector(np.array([_SignedFaceOnes(dd)]))

    from pytools import product
    return product(
        forward_metric_derivative_mv(ambient_dim, rst_axis, dd)
        for rst_axis in range(dim))
Пример #10
0
def dd_axis(axis, ambient_dim, operand):
    """Return the derivative along (XYZ) axis *axis*
    (in *ambient_dim*-dimensional space) of *operand*.
    """
    from pytools.obj_array import is_obj_array, with_object_array_or_scalar
    if is_obj_array(operand):
        def dd_axis_comp(operand_i):
            return dd_axis(axis, ambient_dim, operand_i)

        return with_object_array_or_scalar(dd_axis_comp, operand)

    d = Derivative()

    unit_vector = np.zeros(ambient_dim)
    unit_vector[axis] = 1

    unit_mvector = MultiVector(unit_vector)

    return d.resolve(
            (unit_mvector.scalar_product(d.dnabla(ambient_dim)))
            * d(operand))
Пример #11
0
def parametrization_derivative(ambient_dim, dim=None, dd=None):
    if dim is None:
        dim = ambient_dim

    if dim == 0:
        from pymbolic.geometric_algebra import get_euclidean_space
        return MultiVector(_SignedFaceOnes(dd),
                space=get_euclidean_space(ambient_dim))

    from pytools import product
    return product(
        forward_metric_derivative_mv(ambient_dim, rst_axis, dd)
        for rst_axis in range(dim))
Пример #12
0
def find_volume_mesh_element_group_orientation(vertices, grp):
    """Return a positive floating point number for each positively
    oriented element, and a negative floating point number for
    each negatively oriented element.
    """

    from meshmode.mesh import SimplexElementGroup

    if not isinstance(grp, SimplexElementGroup):
        raise NotImplementedError(
                "finding element orientations "
                "only supported on "
                "exclusively SimplexElementGroup-based meshes")

    # (ambient_dim, nelements, nvertices)
    my_vertices = vertices[:, grp.vertex_indices]

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

    ambient_dim = spanning_vectors.shape[0]
    nspan_vectors = spanning_vectors.shape[-1]

    if ambient_dim != grp.dim:
        raise ValueError("can only find orientation of volume meshes")

    spanning_object_array = np.empty(
            (nspan_vectors, ambient_dim),
            dtype=np.object)

    for ispan in range(nspan_vectors):
        for idim in range(ambient_dim):
            spanning_object_array[ispan, idim] = \
                    spanning_vectors[idim, :, ispan]

    from pymbolic.geometric_algebra import MultiVector

    mvs = [MultiVector(vec) for vec in spanning_object_array]

    from operator import xor
    outer_prod = -reduce(xor, mvs)

    if grp.dim == 1:
        # FIXME: This is a little weird.
        outer_prod = -outer_prod

    return (outer_prod.I | outer_prod).as_scalar()
Пример #13
0
def forward_metric_derivative_mv(actx: ArrayContext,
                                 dcoll: DiscretizationCollection,
                                 rst_axis,
                                 dd=None) -> MultiVector:
    r"""Computes a :class:`pymbolic.geometric_algebra.MultiVector` containing
    the forward metric derivatives of each physical coordinate.

    :arg rst_axis: a :class:`tuple` of tuples indicating indices of
        coordinate axes of the reference element to the number of derivatives
        which will be taken.
    :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one.
        Defaults to the base volume discretization.
    :returns: a :class:`pymbolic.geometric_algebra.MultiVector` containing
        the forward metric derivatives in each physical coordinate.
    """
    return MultiVector(
        forward_metric_derivative_vector(actx, dcoll, rst_axis, dd=dd))
Пример #14
0
    def _normal():
        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:
            result = rel_mv_normal(actx, dcoll, dd=dd)
        else:
            # 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

            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()

            result = mv / actx.np.sqrt(mv.norm_squared())

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

        return freeze(result, actx)
Пример #15
0
 def map_multivector_variable(self, expr):
     from pymbolic.primitives import make_sym_vector
     return MultiVector(
         make_sym_vector(expr.name,
                         self.ambient_dim,
                         var_factory=type(expr)))
Пример #16
0
def test_geometric_algebra(dims):
    pytest.importorskip("numpy")

    import numpy as np
    from pymbolic.geometric_algebra import MultiVector as MV  # noqa

    vec1 = MV(np.random.randn(dims))
    vec2 = MV(np.random.randn(dims))
    vec3 = MV(np.random.randn(dims))
    vec4 = MV(np.random.randn(dims))
    vec5 = MV(np.random.randn(dims))

    # Fundamental identity
    assert ((vec1 ^ vec2) + (vec1 | vec2)).close_to(vec1*vec2)

    # Antisymmetry
    assert (vec1 ^ vec2 ^ vec3).close_to(- vec2 ^ vec1 ^ vec3)

    vecs = [vec1, vec2, vec3, vec4, vec5]

    if len(vecs) > dims:
        from operator import xor as outer
        assert reduce(outer, vecs).close_to(0)

    assert (vec1.inv()*vec1).close_to(1)
    assert (vec1*vec1.inv()).close_to(1)
    assert ((1/vec1)*vec1).close_to(1)
    assert (vec1/vec1).close_to(1)

    for a, b, c in [
            (vec1, vec2, vec3),
            (vec1*vec2, vec3, vec4),
            (vec1, vec2*vec3, vec4),
            (vec1, vec2, vec3*vec4),
            (vec1, vec2, vec3*vec4*vec5),
            (vec1, vec2*vec1, vec3*vec4*vec5),
            ]:

        # Associativity
        assert ((a*b)*c).close_to(a*(b*c))
        assert ((a ^ b) ^ c).close_to(a ^ (b ^ c))
        # The inner product is not associative.

        # scalar product
        assert ((c*b).project(0)) .close_to(b.scalar_product(c))
        assert ((c.rev()*b).project(0)) .close_to(b.rev().scalar_product(c))
        assert ((b.rev()*b).project(0)) .close_to(b.norm_squared())

        assert b.norm_squared() >= 0
        assert c.norm_squared() >= 0

        # Cauchy's inequality
        assert b.scalar_product(c) <= abs(b)*abs(c) + 1e-13

        # contractions

        # (3.18) in [DFM]
        assert abs(b.scalar_product(a ^ c) - (b >> a).scalar_product(c)) < 1e-13

        # duality, (3.20) in [DFM]
        assert ((a ^ b) << c) .close_to(a << (b << c))

        # two definitions of the dual agree: (1.2.26) in [HS]
        # and (sec 3.5.3) in [DFW]
        assert (c << c.I.rev()).close_to(c | c.I.rev())

        # inverse
        for div in list(b.gen_blades()) + [vec1, vec1.I]:
            assert (div.inv()*div).close_to(1)
            assert (div*div.inv()).close_to(1)
            assert ((1/div)*div).close_to(1)
            assert (div/div).close_to(1)
            assert ((c/div)*div).close_to(c)
            assert ((c*div)/div).close_to(c)

        # reverse properties (Sec 2.9.5 [DFM])
        assert c.rev().rev() == c
        assert (b ^ c).rev() .close_to((c.rev() ^ b.rev()))

        # dual properties
        # (1.2.26) in [HS]
        assert c.dual() .close_to(c | c.I.rev())
        assert c.dual() .close_to(c*c.I.rev())

        # involution properties (Sec 2.9.5 DFW)
        assert c.invol().invol() == c
        assert (b ^ c).invol() .close_to((b.invol() ^ c.invol()))

        # commutator properties

        # Jacobi identity (1.1.56c) in [HS] or (8.2) in [DFW]
        assert (a.x(b.x(c)) + b.x(c.x(a)) + c.x(a.x(b))).close_to(0)

        # (1.57) in [HS]
        assert a.x(b*c) .close_to(a.x(b)*c + b*a.x(c))
Пример #17
0
def forward_metric_derivative_mv(ambient_dim, rst_axis, dd=None):
    return MultiVector(
        forward_metric_derivative_vector(ambient_dim, rst_axis, dd=dd))
Пример #18
0
def mv_nodes(ambient_dim, dd=None):
    return MultiVector(nodes(ambient_dim, dd))
Пример #19
0
def make_sym_mv(name, dim, var_factory=None):
    return MultiVector(make_sym_array(name, dim, var_factory))
Пример #20
0
def test_geometric_algebra(dims):
    pytest.importorskip("numpy")

    import numpy as np
    from pymbolic.geometric_algebra import MultiVector as MV  # noqa

    vec1 = MV(np.random.randn(dims))
    vec2 = MV(np.random.randn(dims))
    vec3 = MV(np.random.randn(dims))
    vec4 = MV(np.random.randn(dims))
    vec5 = MV(np.random.randn(dims))

    # Fundamental identity
    assert ((vec1 ^ vec2) + (vec1 | vec2)).close_to(vec1 * vec2)

    # Antisymmetry
    assert (vec1 ^ vec2 ^ vec3).close_to(-vec2 ^ vec1 ^ vec3)

    vecs = [vec1, vec2, vec3, vec4, vec5]

    if len(vecs) > dims:
        from operator import xor as outer
        assert reduce(outer, vecs).close_to(0)

    assert (vec1.inv() * vec1).close_to(1)
    assert (vec1 * vec1.inv()).close_to(1)
    assert ((1 / vec1) * vec1).close_to(1)
    assert (vec1 / vec1).close_to(1)

    for a, b, c in [
        (vec1, vec2, vec3),
        (vec1 * vec2, vec3, vec4),
        (vec1, vec2 * vec3, vec4),
        (vec1, vec2, vec3 * vec4),
        (vec1, vec2, vec3 * vec4 * vec5),
        (vec1, vec2 * vec1, vec3 * vec4 * vec5),
    ]:

        # Associativity
        assert ((a * b) * c).close_to(a * (b * c))
        assert ((a ^ b) ^ c).close_to(a ^ (b ^ c))
        # The inner product is not associative.

        # scalar product
        assert ((c * b).project(0)).close_to(b.scalar_product(c))
        assert ((c.rev() * b).project(0)).close_to(b.rev().scalar_product(c))
        assert ((b.rev() * b).project(0)).close_to(b.norm_squared())

        assert b.norm_squared() >= 0
        assert c.norm_squared() >= 0

        # Cauchy's inequality
        assert b.scalar_product(c) <= abs(b) * abs(c) + 1e-13

        # contractions

        # (3.18) in [DFM]
        assert abs(b.scalar_product(a ^ c) -
                   (b >> a).scalar_product(c)) < 1e-13

        # duality, (3.20) in [DFM]
        assert ((a ^ b) << c).close_to(a << (b << c))

        # two definitions of the dual agree: (1.2.26) in [HS]
        # and (sec 3.5.3) in [DFW]
        assert (c << c.I.rev()).close_to(c | c.I.rev())

        # inverse
        for div in list(b.gen_blades()) + [vec1, vec1.I]:
            assert (div.inv() * div).close_to(1)
            assert (div * div.inv()).close_to(1)
            assert ((1 / div) * div).close_to(1)
            assert (div / div).close_to(1)
            assert ((c / div) * div).close_to(c)
            assert ((c * div) / div).close_to(c)

        # reverse properties (Sec 2.9.5 [DFM])
        assert c.rev().rev() == c
        assert (b ^ c).rev().close_to((c.rev() ^ b.rev()))

        # dual properties
        # (1.2.26) in [HS]
        assert c.dual().close_to(c | c.I.rev())
        assert c.dual().close_to(c * c.I.rev())

        # involution properties (Sec 2.9.5 DFW)
        assert c.invol().invol() == c
        assert (b ^ c).invol().close_to((b.invol() ^ c.invol()))

        # commutator properties

        # Jacobi identity (1.1.56c) in [HS] or (8.2) in [DFW]
        assert (a.x(b.x(c)) + b.x(c.x(a)) + c.x(a.x(b))).close_to(0)

        # (1.57) in [HS]
        assert a.x(b * c).close_to(a.x(b) * c + b * a.x(c))
Пример #21
0
def make_common_subexpression(field, prefix=None, scope=None):
    """Wrap *field* in a :class:`CommonSubexpression` with
    *prefix*. If *field* is a :mod:`numpy` object array,
    each individual entry is instead wrapped. If *field* is a
    :class:`pymbolic.geometric_algebra.MultiVector`, each
    coefficient is individually wrapped.

    See :class:`CommonSubexpression` for the meaning of *prefix*
    and *scope*.
    """

    if isinstance(field, CommonSubexpression) and (scope is None or scope
                                                   == cse_scope.EVALUATION
                                                   or field.scope == scope):
        # Don't re-wrap
        return field

    try:
        from pytools.obj_array import log_shape
    except ImportError:
        have_obj_array = False
    else:
        have_obj_array = True

    if have_obj_array:
        ls = log_shape(field)

    from pymbolic.geometric_algebra import MultiVector
    if isinstance(field, MultiVector):
        new_data = {}
        for bits, coeff in six.iteritems(field.data):
            if prefix is not None:
                blade_str = field.space.blade_bits_to_str(bits, "")
                component_prefix = prefix + "_" + blade_str
            else:
                component_prefix = None

            new_data[bits] = make_common_subexpression(coeff, component_prefix,
                                                       scope)

        return MultiVector(new_data, field.space)

    elif have_obj_array and ls != ():
        from pytools import indices_in_shape
        result = numpy.zeros(ls, dtype=object)

        for i in indices_in_shape(ls):
            if prefix is not None:
                component_prefix = prefix + "_".join(str(i_i) for i_i in i)
            else:
                component_prefix = None

            if is_constant(field[i]):
                result[i] = field[i]
            else:
                result[i] = make_common_subexpression(field[i],
                                                      component_prefix, scope)

        return result
    else:
        if is_constant(field):
            return field
        else:
            return CommonSubexpression(field, prefix, scope)