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) ]))
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) ]))
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()
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))
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)
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)
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
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())
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))
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))
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()
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))
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)
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)
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)))
def forward_metric_derivative_mv(ambient_dim, rst_axis, dd=None): return MultiVector( forward_metric_derivative_vector(ambient_dim, rst_axis, dd=dd))
def mv_nodes(ambient_dim, dd=None): return MultiVector(nodes(ambient_dim, dd))
def make_sym_mv(name, dim, var_factory=None): return MultiVector(make_sym_array(name, dim, var_factory))