def weak_local_d_dx(dcoll, *args): r"""Return the element-local weak derivative along axis *xyz_axis* of the volume function represented by *vec*. May be called with ``(xyz_axis, vecs)`` or ``(dd, xyz_axis, vecs)``. :arg xyz_axis: an integer indicating the axis along which the derivative is taken :arg vec: a :class:`~meshmode.dof_array.DOFArray` :returns: a :class:`~meshmode.dof_array.DOFArray`\ s """ if len(args) == 2: xyz_axis, vec = args dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) elif len(args) == 3: dd, xyz_axis, vec = args else: raise TypeError("invalid number of arguments") return _bound_weak_d_dx(dcoll, dd, xyz_axis)(u=vec)
def weak_local_grad(dcoll, *args): r"""Return the element-local weak gradient of the volume function represented by *vec*. May be called with ``(vecs)`` or ``(dd, vecs)``. :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` :returns: an object array 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") return _bound_weak_grad(dcoll, dd)(u=vec)
def _div_helper(dcoll, diff_func, *args): if len(args) == 1: vecs, = args dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) elif len(args) == 2: dd, vecs = args else: raise TypeError("invalid number of arguments") if not isinstance(vecs, np.ndarray): # vecs is not an object array -> treat as array container return map_array_container(partial(_div_helper, dcoll, diff_func, dd), vecs) assert vecs.dtype == object if vecs.size: sample_vec = vecs[(0, ) * vecs.ndim] if isinstance(sample_vec, np.ndarray): assert sample_vec.dtype == object # vecs is an object array containing further object arrays # -> treat as array container return map_array_container( partial(_div_helper, dcoll, diff_func, dd), vecs) if vecs.shape[-1] != dcoll.ambient_dim: raise ValueError( "last/innermost dimension of *vecs* argument doesn't match " "ambient dimension") div_result_shape = vecs.shape[:-1] if len(div_result_shape) == 0: return sum(diff_func(dd, i, vec_i) for i, vec_i in enumerate(vecs)) else: result = np.zeros(div_result_shape, dtype=object) for idx in np.ndindex(div_result_shape): result[idx] = sum( diff_func(dd, i, vec_i) for i, vec_i in enumerate(vecs[idx])) return result
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 int_tpair(expression, qtag=None, from_dd=None): from meshmode.discretization.connection import FACE_RESTR_INTERIOR from grudge.symbolic.operators import project, OppositeInteriorFaceSwap import grudge.dof_desc as dof_desc if from_dd is None: from_dd = dof_desc.DD_VOLUME assert not from_dd.uses_quadrature() trace_dd = dof_desc.DOFDesc(FACE_RESTR_INTERIOR, qtag) if from_dd.domain_tag == trace_dd.domain_tag: i = expression else: i = project(from_dd, trace_dd.with_qtag(None))(expression) e = cse(OppositeInteriorFaceSwap()(i)) if trace_dd.uses_quadrature(): i = cse(project(trace_dd.with_qtag(None), trace_dd)(i)) e = cse(project(trace_dd.with_qtag(None), trace_dd)(e)) return TracePair(trace_dd, interior=i, exterior=e)
def weak_local_div(dcoll: DiscretizationCollection, *args): r"""Return the element-local weak divergence of the vector volume function represented by *vecs*. May be called with ``(vecs)`` or ``(dd, vecs)``. Specifically, this function computes the volume contribution of the weak divergence of a vector function :math:`\mathbf{f}`, in each element :math:`E`, with respect to polynomial test functions :math:`\phi`: .. math:: \int_E \nabla \phi \cdot \mathbf{f}\,\mathrm{d}x \sim \sum_{i=1}^d \mathbf{D}_{E,i}^T \mathbf{M}_{E}^T\mathbf{f}_i|_E, where :math:`\mathbf{D}_{E,i}` is the polynomial differentiation matrix on an :math:`E` for the :math:`i`-th spatial coordinate, and :math:`\mathbf{M}_E` is the elemental mass matrix (see :func:`mass` for more information). :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 object array of a :class:`~meshmode.dof_array.DOFArray`\ s, where the last axis of the array must have length matching the volume dimension. :returns: a :class:`~meshmode.dof_array.DOFArray`. """ if len(args) == 1: vecs, = args dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) elif len(args) == 2: dd, vecs = args else: raise TypeError("invalid number of arguments") return _div_helper(dcoll, lambda i, subvec: weak_local_d_dx(dcoll, dd, i, subvec), vecs)
def _grad_helper(dcoll, scalar_grad, *args, nested): if len(args) == 1: vec, = args dd_in = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) elif len(args) == 2: dd_in, vec = args else: raise TypeError("invalid number of arguments") if isinstance(vec, np.ndarray): # Occasionally, data structures coming from *mirgecom* will # contain empty object arrays as placeholders for fields. # For example, species mass fractions is an empty object array when # running in a single-species configuration. # This hack here ensures that these empty arrays, at the very least, # have their shape updated when applying the gradient operator if vec.size == 0: return vec.reshape(vec.shape + (dcoll.ambient_dim, )) # For containers with ndarray data (such as momentum/velocity), # the gradient is matrix-valued, so we compute the gradient for # each component. If requested (via 'not nested'), return a matrix of # derivatives by stacking the results. grad = obj_array_vectorize( lambda el: _grad_helper( dcoll, scalar_grad, dd_in, el, nested=nested), vec) if nested: return grad else: return np.stack(grad, axis=0) if not isinstance(vec, DOFArray): return map_array_container( partial(_grad_helper, dcoll, scalar_grad, dd_in, nested=nested), vec) return scalar_grad(dcoll, dd_in, vec)
def mass(dcoll: DiscretizationCollection, *args): r"""Return the action of the DG mass matrix on a vector (or vectors) of :class:`~meshmode.dof_array.DOFArray`\ s, *vec*. In the case of *vec* being an object array of :class:`~meshmode.dof_array.DOFArray`\ s, the mass operator is applied in the Kronecker sense (component-wise). May be called with ``(vec)`` or ``(dd, vec)``. Specifically, this function applies the mass matrix elementwise on a vector of coefficients :math:`\mathbf{f}` via: :math:`\mathbf{M}_{E}\mathbf{f}|_E`, where .. math:: \left(\mathbf{M}_{E}\right)_{ij} = \int_E \phi_i \cdot \phi_j\,\mathrm{d}x, where :math:`\phi_i` are local polynomial basis functions on :math:`E`. :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 object array of :class:`~meshmode.dof_array.DOFArray`\ s. :returns: a :class:`~meshmode.dof_array.DOFArray` denoting the application of the mass matrix, or an object array 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") return _apply_mass_operator(dcoll, dof_desc.DD_VOLUME, dd, vec)
def weak_local_d_dx(dcoll: DiscretizationCollection, *args): r"""Return the element-local weak derivative along axis *xyz_axis* of the volume function represented by *vec*. May be called with ``(xyz_axis, vecs)`` or ``(dd, xyz_axis, vecs)``. Specifically, this function computes the volume contribution of the weak derivative in the :math:`i`-th component (specified by *xyz_axis*) of a function :math:`f`, in each element :math:`E`, with respect to polynomial test functions :math:`\phi`: .. math:: \int_E \partial_i\phi\,f\,\mathrm{d}x \sim \mathbf{D}_{E,i}^T \mathbf{M}_{E}^T\mathbf{f}|_E, where :math:`\mathbf{D}_{E,i}` is the polynomial differentiation matrix on an :math:`E` for the :math:`i`-th spatial coordinate, :math:`\mathbf{M}_E` is the elemental mass matrix (see :func:`mass` for more information), and :math:`\mathbf{f}|_E` is a vector of coefficients for :math:`f` on :math:`E`. :arg xyz_axis: an integer indicating the axis along which the derivative is taken :arg vec: a :class:`~meshmode.dof_array.DOFArray`. :returns: a :class:`~meshmode.dof_array.DOFArray`\ s. """ if len(args) == 2: xyz_axis, vec = args dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) elif len(args) == 3: dd, xyz_axis, vec = args else: raise TypeError("invalid number of arguments") return _apply_stiffness_transpose_operator(dcoll, dof_desc.DD_VOLUME, dd, vec, xyz_axis)
def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) # {{{ parameters # sphere radius radius = 1.0 # sphere resolution resolution = 64 if dim == 2 else 1 # cfl dt_factor = 2.0 # final time final_time = np.pi # velocity field sym_x = sym.nodes(dim) c = make_obj_array([-sym_x[1], sym_x[0], 0.0])[:dim] # flux flux_type = "lf" # }}} # {{{ discretization if dim == 2: from meshmode.mesh.generation import make_curve_mesh, ellipse mesh = make_curve_mesh(lambda t: radius * ellipse(1.0, t), np.linspace(0.0, 1.0, resolution + 1), order) elif dim == 3: from meshmode.mesh.generation import generate_icosphere mesh = generate_icosphere(radius, order=4 * order, uniform_refinement_rounds=resolution) else: raise ValueError("unsupported dimension") discr_tag_to_group_factory = {} if product_tag == "none": product_tag = None else: product_tag = dof_desc.DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory, \ QuadratureSimplexGroupFactory discr_tag_to_group_factory[dof_desc.DISCR_TAG_BASE] = \ PolynomialWarpAndBlendGroupFactory(order) if product_tag: discr_tag_to_group_factory[product_tag] = \ QuadratureSimplexGroupFactory(order=4*order) from grudge import DiscretizationCollection discr = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory=discr_tag_to_group_factory) volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume_discr.ndofs) logger.info("nelements: %d", volume_discr.mesh.nelements) # }}} # {{{ symbolic operators def f_initial_condition(x): return x[0] from grudge.models.advection import SurfaceAdvectionOperator op = SurfaceAdvectionOperator(c, flux_type=flux_type, quad_tag=product_tag) bound_op = bind(discr, op.sym_operator()) u0 = bind(discr, f_initial_condition(sym_x))(actx, t=0) def rhs(t, u): return bound_op(actx, t=t, u=u) # check velocity is tangential sym_normal = sym.surface_normal(dim, dim=dim - 1, dd=dof_desc.DD_VOLUME).as_vector() error = bind(discr, sym.norm(2, c.dot(sym_normal)))(actx) logger.info("u_dot_n: %.5e", error) # }}} # {{{ time stepping # compute time step h_min = bind(discr, sym.h_max_from_volume(discr.ambient_dim, dim=discr.dim))(actx) dt = dt_factor * h_min / order**2 nsteps = int(final_time // dt) + 1 dt = final_time / nsteps + 1.0e-15 logger.info("dt: %.5e", dt) logger.info("nsteps: %d", nsteps) from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u0, rhs) plot = Plotter(actx, discr, order, visualize=visualize) norm = bind(discr, sym.norm(2, sym.var("u"))) norm_u = norm(actx, u=u0) step = 0 event = dt_stepper.StateComputed(0.0, 0, 0, u0) plot(event, "fld-surface-%04d" % 0) if visualize and dim == 3: from grudge.shortcuts import make_visualizer vis = make_visualizer(discr) vis.write_vtk_file("fld-surface-velocity.vtu", [("u", bind(discr, c)(actx)), ("n", bind(discr, sym_normal)(actx))], overwrite=True) df = dof_desc.DOFDesc(FACE_RESTR_INTERIOR) face_discr = discr.connection_from_dds(dof_desc.DD_VOLUME, df).to_discr face_normal = bind( discr, sym.normal(df, face_discr.ambient_dim, dim=face_discr.dim))(actx) from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, face_discr) vis.write_vtk_file("fld-surface-face-normals.vtu", [("n", face_normal)], overwrite=True) for event in dt_stepper.run(t_end=final_time, max_steps=nsteps + 1): if not isinstance(event, dt_stepper.StateComputed): continue step += 1 if step % 10 == 0: norm_u = norm(actx, u=event.state_component) plot(event, "fld-surface-%04d" % step) logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) plot(event, "fld-surface-%04d" % step)
def zero_inflow_bc(dtag, t=0): dd = dof_desc.DOFDesc(dtag, qtag) return dcoll.discr_from_dd(dd).zeros(actx)
def test_mass_mat_trig(actx_factory, ambient_dim, discr_tag): """Check the integral of some trig functions on an interval using the mass matrix. """ actx = actx_factory() nel_1d = 16 order = 4 a = -4.0 * np.pi b = +9.0 * np.pi true_integral = 13 * np.pi / 2 * (b - a)**(ambient_dim - 1) from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory dd_quad = dof_desc.DOFDesc(dof_desc.DTAG_VOLUME_ALL, discr_tag) if discr_tag is dof_desc.DISCR_TAG_BASE: discr_tag_to_group_factory = {} else: discr_tag_to_group_factory = { discr_tag: QuadratureSimplexGroupFactory(order=2 * order) } mesh = mgen.generate_regular_rect_mesh(a=(a, ) * ambient_dim, b=(b, ) * ambient_dim, nelements_per_axis=(nel_1d, ) * ambient_dim, order=1) discr = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory) def _get_variables_on(dd): sym_f = sym.var("f", dd=dd) sym_x = sym.nodes(ambient_dim, dd=dd) sym_ones = sym.Ones(dd) return sym_f, sym_x, sym_ones sym_f, sym_x, sym_ones = _get_variables_on(dof_desc.DD_VOLUME) f_volm = actx.to_numpy(flatten(bind(discr, sym.cos(sym_x[0])**2)(actx))) ones_volm = actx.to_numpy(flatten(bind(discr, sym_ones)(actx))) sym_f, sym_x, sym_ones = _get_variables_on(dd_quad) f_quad = bind(discr, sym.cos(sym_x[0])**2)(actx) ones_quad = bind(discr, sym_ones)(actx) mass_op = bind(discr, sym.MassOperator(dd_quad, dof_desc.DD_VOLUME)(sym_f)) num_integral_1 = np.dot(ones_volm, actx.to_numpy(flatten(mass_op(f=f_quad)))) err_1 = abs(num_integral_1 - true_integral) assert err_1 < 2e-9, err_1 num_integral_2 = np.dot(f_volm, actx.to_numpy(flatten(mass_op(f=ones_quad)))) err_2 = abs(num_integral_2 - true_integral) assert err_2 < 2e-9, err_2 if discr_tag is dof_desc.DISCR_TAG_BASE: # NOTE: `integral` always makes a square mass matrix and # `QuadratureSimplexGroupFactory` does not have a `mass_matrix` method. num_integral_3 = bind(discr, sym.integral(sym_f, dd=dd_quad))(f=f_quad) err_3 = abs(num_integral_3 - true_integral) assert err_3 < 5.0e-10, err_3
def weak_local_d_dx(dcoll: DiscretizationCollection, *args) -> ArrayOrContainerT: r"""Return the element-local weak derivative along axis *xyz_axis* of the volume function represented by *vec*. May be called with ``(xyz_axis, vec)`` or ``(dd_in, xyz_axis, vec)``. Specifically, this function computes the volume contribution of the weak derivative in the :math:`i`-th component (specified by *xyz_axis*) of a function :math:`f`, in each element :math:`E`, with respect to polynomial test functions :math:`\phi`: .. math:: \int_E \partial_i\phi\,f\,\mathrm{d}x \sim \mathbf{D}_{E,i}^T \mathbf{M}_{E}^T\mathbf{f}|_E, where :math:`\mathbf{D}_{E,i}` is the polynomial differentiation matrix on an :math:`E` for the :math:`i`-th spatial coordinate, :math:`\mathbf{M}_E` is the elemental mass matrix (see :func:`mass` for more information), and :math:`\mathbf{f}|_E` is a vector of coefficients for :math:`f` on :math:`E`. :arg dd_in: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization if not provided. :arg xyz_axis: an integer indicating the axis along which the derivative is taken. :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` of them. """ if len(args) == 2: xyz_axis, vec = args dd_in = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) elif len(args) == 3: dd_in, xyz_axis, vec = args else: raise TypeError("invalid number of arguments") if not isinstance(vec, DOFArray): return map_array_container( partial(weak_local_d_dx, dcoll, dd_in, xyz_axis), vec) from grudge.geometry import inverse_surface_metric_derivative_mat in_discr = dcoll.discr_from_dd(dd_in) out_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) actx = vec.array_context inverse_jac_mat = inverse_surface_metric_derivative_mat( actx, dcoll, dd=dd_in, times_area_element=True, _use_geoderiv_connection=actx.supports_nonscalar_broadcasting) return _single_axis_derivative_kernel( actx, out_discr, in_discr, _reference_stiffness_transpose_matrix, inverse_jac_mat, xyz_axis, vec, metric_in_matvec=True)
def test_mass_mat_trig(actx_factory, ambient_dim, discr_tag): """Check the integral of some trig functions on an interval using the mass matrix. """ actx = actx_factory() nel_1d = 16 order = 4 a = -4.0 * np.pi b = +9.0 * np.pi true_integral = 13 * np.pi / 2 * (b - a)**(ambient_dim - 1) from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory dd_quad = dof_desc.DOFDesc(dof_desc.DTAG_VOLUME_ALL, discr_tag) if discr_tag is dof_desc.DISCR_TAG_BASE: discr_tag_to_group_factory = {} else: discr_tag_to_group_factory = { discr_tag: QuadratureSimplexGroupFactory(order=2 * order) } mesh = mgen.generate_regular_rect_mesh(a=(a, ) * ambient_dim, b=(b, ) * ambient_dim, nelements_per_axis=(nel_1d, ) * ambient_dim, order=1) dcoll = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory) def f(x): return actx.np.sin(x[0])**2 volm_disc = dcoll.discr_from_dd(dof_desc.DD_VOLUME) x_volm = thaw(volm_disc.nodes(), actx) f_volm = f(x_volm) ones_volm = volm_disc.zeros(actx) + 1 quad_disc = dcoll.discr_from_dd(dd_quad) x_quad = thaw(quad_disc.nodes(), actx) f_quad = f(x_quad) ones_quad = quad_disc.zeros(actx) + 1 mop_1 = op.mass(dcoll, dd_quad, f_quad) num_integral_1 = op.nodal_sum(dcoll, dof_desc.DD_VOLUME, ones_volm * mop_1) err_1 = abs(num_integral_1 - true_integral) assert err_1 < 2e-9, err_1 mop_2 = op.mass(dcoll, dd_quad, ones_quad) num_integral_2 = op.nodal_sum(dcoll, dof_desc.DD_VOLUME, f_volm * mop_2) err_2 = abs(num_integral_2 - true_integral) assert err_2 < 2e-9, err_2 if discr_tag is dof_desc.DISCR_TAG_BASE: # NOTE: `integral` always makes a square mass matrix and # `QuadratureSimplexGroupFactory` does not have a `mass_matrix` method. num_integral_3 = op.nodal_sum(dcoll, dof_desc.DD_VOLUME, f_quad * mop_2) err_3 = abs(num_integral_3 - true_integral) assert err_3 < 5e-10, err_3
def main(ctx_factory, dim=2, order=4, use_quad=False, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), force_device_scalars=True, ) # {{{ parameters # sphere radius radius = 1.0 # sphere resolution resolution = 64 if dim == 2 else 1 # final time final_time = np.pi # flux flux_type = "lf" # }}} # {{{ discretization if dim == 2: from meshmode.mesh.generation import make_curve_mesh, ellipse mesh = make_curve_mesh( lambda t: radius * ellipse(1.0, t), np.linspace(0.0, 1.0, resolution + 1), order) elif dim == 3: from meshmode.mesh.generation import generate_icosphere mesh = generate_icosphere(radius, order=4 * order, uniform_refinement_rounds=resolution) else: raise ValueError("unsupported dimension") discr_tag_to_group_factory = {} if use_quad: qtag = dof_desc.DISCR_TAG_QUAD else: qtag = None from meshmode.discretization.poly_element import \ default_simplex_group_factory, \ QuadratureSimplexGroupFactory discr_tag_to_group_factory[dof_desc.DISCR_TAG_BASE] = \ default_simplex_group_factory(base_dim=dim-1, order=order) if use_quad: discr_tag_to_group_factory[qtag] = \ QuadratureSimplexGroupFactory(order=4*order) from grudge import DiscretizationCollection dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory=discr_tag_to_group_factory ) volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume_discr.ndofs) logger.info("nelements: %d", volume_discr.mesh.nelements) # }}} # {{{ Surface advection operator # velocity field x = thaw(dcoll.nodes(), actx) c = make_obj_array([-x[1], x[0], 0.0])[:dim] def f_initial_condition(x): return x[0] from grudge.models.advection import SurfaceAdvectionOperator adv_operator = SurfaceAdvectionOperator( dcoll, c, flux_type=flux_type, quad_tag=qtag ) u0 = f_initial_condition(x) def rhs(t, u): return adv_operator.operator(t, u) # check velocity is tangential from grudge.geometry import normal surf_normal = normal(actx, dcoll, dd=dof_desc.DD_VOLUME) error = op.norm(dcoll, c.dot(surf_normal), 2) logger.info("u_dot_n: %.5e", error) # }}} # {{{ time stepping # FIXME: dt estimate is not necessarily valid for surfaces dt = actx.to_numpy( 0.45 * adv_operator.estimate_rk4_timestep(actx, dcoll, fields=u0)) nsteps = int(final_time // dt) + 1 logger.info("dt: %.5e", dt) logger.info("nsteps: %d", nsteps) from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u0, rhs) plot = Plotter(actx, dcoll, order, visualize=visualize) norm_u = actx.to_numpy(op.norm(dcoll, u0, 2)) step = 0 event = dt_stepper.StateComputed(0.0, 0, 0, u0) plot(event, "fld-surface-%04d" % 0) if visualize and dim == 3: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll) vis.write_vtk_file( "fld-surface-velocity.vtu", [ ("u", c), ("n", surf_normal) ], overwrite=True ) df = dof_desc.DOFDesc(FACE_RESTR_INTERIOR) face_discr = dcoll.discr_from_dd(df) face_normal = thaw(dcoll.normal(dd=df), actx) from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, face_discr) vis.write_vtk_file("fld-surface-face-normals.vtu", [ ("n", face_normal) ], overwrite=True) for event in dt_stepper.run(t_end=final_time, max_steps=nsteps + 1): if not isinstance(event, dt_stepper.StateComputed): continue step += 1 if step % 10 == 0: norm_u = actx.to_numpy(op.norm(dcoll, event.state_component, 2)) plot(event, "fld-surface-%04d" % step) logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) # NOTE: These are here to ensure the solution is bounded for the # time interval specified assert norm_u < 3
def _apply_elementwise_reduction(op_name: str, dcoll: DiscretizationCollection, *args) -> ArrayOrContainerT: r"""Returns a vector of DOFs with all entries on each element set to the reduction operation *op_name* over all degrees of freedom. May be called with ``(vec)`` or ``(dd, vec)``. :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`. :returns: a :class:`~meshmode.dof_array.DOFArray` or an :class:`~arraycontext.container.ArrayContainer`. """ 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 not isinstance(vec, DOFArray): return map_array_container( partial(_apply_elementwise_reduction, op_name, dcoll, dd), vec) actx = vec.array_context if actx.supports_nonscalar_broadcasting: return DOFArray(actx, data=tuple( actx.np.broadcast_to((getattr(actx.np, op_name)( vec_i, axis=1).reshape(-1, 1)), vec_i.shape) for vec_i in vec)) else: @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))