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