def main(write_output=True): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim=2, order=4, n=6) discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=4) sym_op = sym.normal(sym.BTAG_ALL, mesh.dim) #sym_op = sym.nodes(mesh.dim, where=sym.BTAG_ALL) print(sym.pretty(sym_op)) op = bind(discr, sym_op) print() print(op.eval_code) vec = op(queue) vis = shortcuts.make_visualizer(discr, 4) vis.write_vtk_file("geo.vtu", [ ]) bvis = shortcuts.make_boundary_visualizer(discr, 4) bvis.write_vtk_file("bgeo.vtu", [ ("normals", vec) ])
def absorbing_bc(self, w): """Construct part of the flux operator template for 1st order absorbing boundary conditions. """ absorb_normal = sym.normal(self.absorb_tag, self.dimensions) e, h = self.split_eh(w) if self.fixed_material: epsilon = self.epsilon mu = self.mu absorb_Z = (mu/epsilon)**0.5 # noqa: N806 absorb_Y = 1/absorb_Z # noqa: N806 absorb_e = sym.cse(sym.project("vol", self.absorb_tag)(e)) absorb_h = sym.cse(sym.project("vol", self.absorb_tag)(h)) bc = flat_obj_array( absorb_e + 1/2*(self.space_cross_h(absorb_normal, self.space_cross_e( absorb_normal, absorb_e)) - absorb_Z*self.space_cross_h(absorb_normal, absorb_h)), absorb_h + 1/2*( self.space_cross_e(absorb_normal, self.space_cross_h( absorb_normal, absorb_h)) + absorb_Y*self.space_cross_e(absorb_normal, absorb_e))) return bc
def main(write_output=True): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) from meshmode.mesh import BTAG_ALL from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim=2, order=4, nelements_side=6) discr = DiscretizationCollection(actx, mesh, order=4) sym_op = sym.normal(BTAG_ALL, mesh.dim) # sym_op = sym.nodes(mesh.dim, dd=BTAG_ALL) print(sym.pretty(sym_op)) op = bind(discr, sym_op) print() print(op.eval_code) vec = op(actx) vis = shortcuts.make_visualizer(discr) vis.write_vtk_file("geo.vtu", []) bvis = shortcuts.make_boundary_visualizer(discr) bvis.write_vtk_file("bgeo.vtu", [("normals", vec)])
def sym_operator(self): d = self.ambient_dim w = sym.make_sym_array("w", d + 1) u = w[0] v = w[1:] # boundary conditions ------------------------------------------------- # dirichlet BCs ------------------------------------------------------- dir_u = sym.cse(sym.interp("vol", self.dirichlet_tag)(u)) dir_v = sym.cse(sym.interp("vol", self.dirichlet_tag)(v)) if self.dirichlet_bc_f: # FIXME from warnings import warn warn("Inhomogeneous Dirichlet conditions on the wave equation " "are still having issues.") dir_g = sym.Field("dir_bc_u") dir_bc = join_fields(2 * dir_g - dir_u, dir_v) else: dir_bc = join_fields(-dir_u, dir_v) dir_bc = sym.cse(dir_bc, "dir_bc") # neumann BCs --------------------------------------------------------- neu_u = sym.cse(sym.interp("vol", self.neumann_tag)(u)) neu_v = sym.cse(sym.interp("vol", self.neumann_tag)(v)) neu_bc = sym.cse(join_fields(neu_u, -neu_v), "neu_bc") # radiation BCs ------------------------------------------------------- rad_normal = sym.normal(self.radiation_tag, d) rad_u = sym.cse(sym.interp("vol", self.radiation_tag)(u)) rad_v = sym.cse(sym.interp("vol", self.radiation_tag)(v)) rad_bc = sym.cse( join_fields( 0.5 * (rad_u - self.sign * np.dot(rad_normal, rad_v)), 0.5 * rad_normal * (np.dot(rad_normal, rad_v) - self.sign * rad_u)), "rad_bc") # entire operator ----------------------------------------------------- def flux(pair): return sym.interp(pair.dd, "all_faces")(self.flux(pair)) result = sym.InverseMassOperator()( join_fields(-self.c * np.dot(sym.stiffness_t(self.ambient_dim), v), -self.c * (sym.stiffness_t(self.ambient_dim) * u)) - sym.FaceMassOperator() (flux(sym.int_tpair(w)) + flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc)) + flux(sym.bv_tpair(self.neumann_tag, w, neu_bc)) + flux(sym.bv_tpair(self.radiation_tag, w, rad_bc)))) result[0] += self.source_f return result
def normal(self, dd): surface_discr = self.discr_from_dd(dd) actx = surface_discr._setup_actx return freeze( bind(self, sym.normal(dd, surface_discr.ambient_dim, surface_discr.dim), local_only=True) (array_context=actx))
def flux(self, w): """The numerical flux for variable coefficients. :param flux_type: can be in [0,1] for anything between central and upwind, or "lf" for Lax-Friedrichs. As per Hesthaven and Warburton page 433. """ normal = sym.normal(w.dd, self.dimensions) if self.fixed_material: e, h = self.split_eh(w) epsilon = self.epsilon mu = self.mu Z_int = (mu/epsilon)**0.5 # noqa: N806 Y_int = 1/Z_int # noqa: N806 Z_ext = (mu/epsilon)**0.5 # noqa: N806 Y_ext = 1/Z_ext # noqa: N806 if self.flux_type == "lf": # if self.fixed_material: # max_c = (self.epsilon*self.mu)**(-0.5) return flat_obj_array( # flux e, 1/2*( -self.space_cross_h(normal, h.ext-h.int) # multiplication by epsilon undoes material divisor below #-max_c*(epsilon*e.int - epsilon*e.ext) ), # flux h 1/2*( self.space_cross_e(normal, e.ext-e.int) # multiplication by mu undoes material divisor below #-max_c*(mu*h.int - mu*h.ext) )) elif isinstance(self.flux_type, (int, float)): # see doc/maxima/maxwell.mac return flat_obj_array( # flux e, ( -1/(Z_int+Z_ext)*self.space_cross_h(normal, Z_ext*(h.ext-h.int) - self.flux_type*self.space_cross_e(normal, e.ext-e.int)) ), # flux h ( 1/(Y_int + Y_ext)*self.space_cross_e(normal, Y_ext*(e.ext-e.int) + self.flux_type*self.space_cross_h(normal, h.ext-h.int)) ), ) else: raise ValueError("maxwell: invalid flux_type (%s)" % self.flux_type)
def v_dot_n_tpair(velocity, dd=None): if dd is None: dd = sym.DOFDesc(sym.FACE_RESTR_INTERIOR) ambient_dim = len(velocity) normal = sym.normal(dd.with_qtag(None), ambient_dim, dim=ambient_dim - 2) return sym.int_tpair(velocity.dot(normal), qtag=dd.quadrature_tag, from_dd=dd.with_qtag(None))
def normal(dcoll, dd): """Get unit normal to specified surface discretization, *dd*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization. :returns: an object array of :class:`~meshmode.dof_array.DOFArray`. """ surface_discr = dcoll.discr_from_dd(dd) actx = surface_discr._setup_actx return freeze( bind(dcoll, sym.normal(dd, surface_discr.ambient_dim, surface_discr.dim), local_only=True)(array_context=actx))
def get_strong_wave_op_with_discr_direct(cl_ctx, dims=2, order=4): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dims, b=(0.5, ) * dims, n=(16, ) * dims) logger.debug("%d elements", mesh.nelements) discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order) source_center = np.array([0.1, 0.22, 0.33])[:dims] source_width = 0.05 source_omega = 3 sym_x = sym.nodes(mesh.dim) sym_source_center_dist = sym_x - source_center sym_t = sym.ScalarVariable("t") from meshmode.mesh import BTAG_ALL c = -0.1 sign = -1 w = sym.make_sym_array("w", dims + 1) u = w[0] v = w[1:] source_f = ( sym.sin(source_omega * sym_t) * sym.exp(-np.dot(sym_source_center_dist, sym_source_center_dist) / source_width**2)) rad_normal = sym.normal(BTAG_ALL, dims) rad_u = sym.cse(sym.interp("vol", BTAG_ALL)(u)) rad_v = sym.cse(sym.interp("vol", BTAG_ALL)(v)) rad_bc = sym.cse( sym.join_fields( 0.5 * (rad_u - sign * np.dot(rad_normal, rad_v)), 0.5 * rad_normal * (np.dot(rad_normal, rad_v) - sign * rad_u)), "rad_bc") sym_operator = ( -sym.join_fields(-c * np.dot(sym.nabla(dims), v) - source_f, -c * (sym.nabla(dims) * u)) + sym.InverseMassOperator()( sym.FaceMassOperator() (dg_flux(c, sym.int_tpair(w)) + dg_flux(c, sym.bv_tpair(BTAG_ALL, w, rad_bc))))) return (sym_operator, discr)
def v_dot_n_tpair(velocity, dd=None): from grudge.dof_desc import DOFDesc from meshmode.discretization.connection import FACE_RESTR_INTERIOR if dd is None: dd = DOFDesc(FACE_RESTR_INTERIOR) ambient_dim = len(velocity) normal = sym.normal(dd.with_discr_tag(None), ambient_dim, dim=ambient_dim - 2) return sym.int_tpair(velocity.dot(normal), qtag=dd.discretization_tag, from_dd=dd.with_discr_tag(None))
def flux(self, w): u = w[0] v = w[1:] normal = sym.normal(w.dd, self.ambient_dim) flux_weak = flat_obj_array(np.dot(v.avg, normal), u.avg * normal) if self.flux_type == "central": return -self.c * flux_weak elif self.flux_type == "upwind": return -self.c * (flux_weak + self.sign * flat_obj_array( 0.5 * (u.int - u.ext), 0.5 * (normal * np.dot(normal, v.int - v.ext)))) else: raise ValueError("invalid flux type '%s'" % self.flux_type)
def dg_flux(c, tpair): u = tpair[0] v = tpair[1:] dims = len(v) normal = sym.normal(tpair.dd, dims) flux_weak = flat_obj_array(np.dot(v.avg, normal), u.avg * normal) flux_weak -= (1 if c > 0 else -1) * flat_obj_array( 0.5 * (u.int - u.ext), 0.5 * (normal * np.dot(normal, v.int - v.ext))) flux_strong = flat_obj_array(np.dot(v.int, normal), u.int * normal) - flux_weak return sym.project(tpair.dd, "all_faces")(c * flux_strong)
def dg_flux(c, tpair): u = tpair[0] v = tpair[1:] dims = len(v) normal = sym.normal(tpair.dd, dims) flux_weak = sym.join_fields(np.dot(v.avg, normal), u.avg * normal) flux_weak -= (1 if c > 0 else -1) * sym.join_fields( 0.5 * (u.int - u.ext), 0.5 * (normal * np.dot(normal, v.int - v.ext))) flux_strong = sym.join_fields(np.dot(v.int, normal), u.int * normal) - flux_weak return sym.interp(tpair.dd, "all_faces")(c * flux_strong)
def test_empty_boundary(actx_factory): # https://github.com/inducer/grudge/issues/54 from meshmode.mesh import BTAG_NONE actx = actx_factory() dim = 2 mesh = mgen.generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(8, ) * dim, order=4) discr = DiscretizationCollection(actx, mesh, order=4) normal = bind(discr, sym.normal(BTAG_NONE, dim, dim=dim - 1))(actx) from meshmode.dof_array import DOFArray for component in normal: assert isinstance(component, DOFArray) assert len(component) == len(discr.discr_from_dd(BTAG_NONE).groups)
def weak_flux(self, u): normal = sym.normal(u. dd, self.ambient_dim) v_dot_normal = sym.cse(self.v.dot(normal), "v_dot_normal") norm_v = sym.sqrt((self.v**2).sum()) if self.flux_type == "central": return u.avg*v_dot_normal elif self.flux_type == "lf": return u.avg*v_dot_normal + 0.5*norm_v*(u.int - u.ext) elif self.flux_type == "upwind": return ( v_dot_normal * sym.If( sym.Comparison(v_dot_normal, ">", 0), u.int, # outflow u.ext, # inflow )) else: raise ValueError("invalid flux type")
def advection_weak_flux(flux_type, u, velocity): normal = sym.normal(u.dd, len(velocity)) v_dot_n = sym.cse(velocity.dot(normal), "v_dot_normal") flux_type = flux_type.lower() if flux_type == "central": return u.avg * v_dot_n elif flux_type == "lf": norm_v = sym.sqrt((velocity**2).sum()) return u.avg * v_dot_n + 0.5 * norm_v * (u.int - u.ext) elif flux_type == "upwind": u_upwind = sym.If( sym.Comparison(v_dot_n, ">", 0), u.int, # outflow u.ext # inflow ) return u_upwind * v_dot_n else: raise ValueError("flux `{}` is not implemented".format(flux_type))
def flux(self, w): c = w[0] u = w[1] v = w[2:] normal = sym.normal(w.dd, self.ambient_dim) if self.flux_type == "central": return -0.5 * flat_obj_array( np.dot(v.int * c.int + v.ext * c.ext, normal), (u.int * c.int + u.ext * c.ext) * normal) elif self.flux_type == "upwind": return -0.5 * flat_obj_array( np.dot(normal, c.ext * v.ext + c.int * v.int) + c.ext * u.ext - c.int * u.int, normal * (np.dot(normal, c.ext * v.ext - c.int * v.int) + c.ext * u.ext + c.int * u.int)) else: raise ValueError("invalid flux type '%s'" % self.flux_type)
def flux(self, w): u = w[0] v = w[1:] normal = sym.normal(w.dd, self.ambient_dim) flux_weak = flat_obj_array(np.dot(v.avg, normal), u.avg * normal) if self.flux_type == "central": pass elif self.flux_type == "upwind": # see doc/notes/grudge-notes.tm flux_weak -= self.sign * flat_obj_array( 0.5 * (u.int - u.ext), 0.5 * (normal * np.dot(normal, v.int - v.ext))) else: raise ValueError("invalid flux type '%s'" % self.flux_type) flux_strong = flat_obj_array(np.dot(v.int, normal), u.int * normal) - flux_weak return self.c * flux_strong
def test_2d_gauss_theorem(actx_factory): """Verify Gauss's theorem explicitly on a mesh""" pytest.importorskip("meshpy") from meshpy.geometry import make_circle, GeometryBuilder from meshpy.triangle import MeshInfo, build geob = GeometryBuilder() geob.add_geometry(*make_circle(1)) mesh_info = MeshInfo() geob.set(mesh_info) mesh_info = build(mesh_info) from meshmode.mesh.io import from_meshpy mesh = from_meshpy(mesh_info, order=1) actx = actx_factory() discr = DGDiscretizationWithBoundaries(actx, mesh, order=2) def f(x): return flat_obj_array( sym.sin(3*x[0])+sym.cos(3*x[1]), sym.sin(2*x[0])+sym.cos(x[1])) gauss_err = bind(discr, sym.integral(( sym.nabla(2) * f(sym.nodes(2)) ).sum()) - # noqa: W504 sym.integral( sym.project("vol", sym.BTAG_ALL)(f(sym.nodes(2))) .dot(sym.normal(sym.BTAG_ALL, 2)), dd=sym.BTAG_ALL) )(actx) assert abs(gauss_err) < 1e-13
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 test_face_normal_surface(actx_factory, mesh_name): """Check that face normals are orthogonal to the surface normal""" actx = actx_factory() # {{{ geometry if mesh_name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif mesh_name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() else: raise ValueError("unknown mesh name: %s" % mesh_name) mesh = builder.get_mesh(builder.resolutions[0], builder.mesh_order) discr = DiscretizationCollection(actx, mesh, order=builder.order) 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 from meshmode.discretization.connection import FACE_RESTR_INTERIOR dv = dof_desc.DD_VOLUME df = dof_desc.as_dofdesc(FACE_RESTR_INTERIOR) ambient_dim = mesh.ambient_dim dim = mesh.dim sym_surf_normal = sym.project(dv, df)(sym.surface_normal(ambient_dim, dim=dim, dd=dv).as_vector()) sym_surf_normal = sym_surf_normal / sym.sqrt(sum(sym_surf_normal**2)) sym_face_normal_i = sym.normal(df, ambient_dim, dim=dim - 1) sym_face_normal_e = sym.OppositeInteriorFaceSwap(df)(sym_face_normal_i) if mesh.ambient_dim == 3: # NOTE: there's only one face tangent in 3d sym_face_tangent = ( sym.pseudoscalar(ambient_dim, dim - 1, dd=df) / sym.area_element(ambient_dim, dim - 1, dd=df)).as_vector() # }}} # {{{ checks def _eval_error(x): return bind(discr, sym.norm(np.inf, sym.var("x", dd=df), dd=df))(actx, x=x) rtol = 1.0e-14 surf_normal = bind(discr, sym_surf_normal)(actx) face_normal_i = bind(discr, sym_face_normal_i)(actx) face_normal_e = bind(discr, sym_face_normal_e)(actx) # check interpolated surface normal is orthogonal to face normal error = _eval_error(surf_normal.dot(face_normal_i)) logger.info("error[n_dot_i]: %.5e", error) assert error < rtol # check angle between two neighboring elements error = _eval_error(face_normal_i.dot(face_normal_e) + 1.0) logger.info("error[i_dot_e]: %.5e", error) assert error > rtol # check orthogonality with face tangent if ambient_dim == 3: face_tangent = bind(discr, sym_face_tangent)(actx) error = _eval_error(face_tangent.dot(face_normal_i)) logger.info("error[t_dot_i]: %.5e", error) assert error < 5 * rtol
def flux(self, u): normal = sym.normal(u.dd, self.ambient_dim) v_dot_normal = sym.cse(self.v.dot(normal), "v_dot_normal") return u.int * v_dot_normal - self.weak_flux(u)
def test_surface_divergence_theorem(actx_factory, mesh_name, visualize=False): r"""Check the surface divergence theorem. .. math:: \int_Sigma \phi \nabla_i f_i = \int_\Sigma \nabla_i \phi f_i + \int_\Sigma \kappa \phi f_i n_i + \int_{\partial \Sigma} \phi f_i m_i where :math:`n_i` is the surface normal and :class:`m_i` is the face normal (which should be orthogonal to both the surface normal and the face tangent). """ actx = actx_factory() # {{{ cases if mesh_name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif mesh_name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() elif mesh_name == "circle": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=1.0, aspect_ratio=1.0) elif mesh_name == "starfish": from mesh_data import StarfishMeshBuilder builder = StarfishMeshBuilder() elif mesh_name == "sphere": from mesh_data import SphereMeshBuilder builder = SphereMeshBuilder(radius=1.0, mesh_order=16) else: raise ValueError("unknown mesh name: %s" % mesh_name) # }}} # {{{ convergene def f(x): return flat_obj_array( sym.sin(3 * x[1]) + sym.cos(3 * x[0]) + 1.0, sym.sin(2 * x[0]) + sym.cos(x[1]), 3.0 * sym.cos(x[0] / 2) + sym.cos(x[1]), )[:ambient_dim] from pytools.convergence import EOCRecorder eoc_global = EOCRecorder() eoc_local = EOCRecorder() theta = np.pi / 3.33 ambient_dim = builder.ambient_dim if ambient_dim == 2: mesh_rotation = np.array([ [np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)], ]) else: mesh_rotation = np.array([ [1.0, 0.0, 0.0], [0.0, np.cos(theta), -np.sin(theta)], [0.0, np.sin(theta), np.cos(theta)], ]) mesh_offset = np.array([0.33, -0.21, 0.0])[:ambient_dim] for i, resolution in enumerate(builder.resolutions): from meshmode.mesh.processing import affine_map from meshmode.discretization.connection import FACE_RESTR_ALL mesh = builder.get_mesh(resolution, builder.mesh_order) mesh = affine_map(mesh, A=mesh_rotation, b=mesh_offset) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory discr = DiscretizationCollection(actx, mesh, order=builder.order, discr_tag_to_group_factory={ "product": QuadratureSimplexGroupFactory( 2 * builder.order) }) volume = discr.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume.ndofs) logger.info("nelements: %d", volume.mesh.nelements) dd = dof_desc.DD_VOLUME dq = dd.with_discr_tag("product") df = dof_desc.as_dofdesc(FACE_RESTR_ALL) ambient_dim = discr.ambient_dim dim = discr.dim # variables sym_f = f(sym.nodes(ambient_dim, dd=dd)) sym_f_quad = f(sym.nodes(ambient_dim, dd=dq)) sym_kappa = sym.summed_curvature(ambient_dim, dim=dim, dd=dq) sym_normal = sym.surface_normal(ambient_dim, dim=dim, dd=dq).as_vector() sym_face_normal = sym.normal(df, ambient_dim, dim=dim - 1) sym_face_f = sym.project(dd, df)(sym_f) # operators sym_stiff = sum( sym.StiffnessOperator(d)(f) for d, f in enumerate(sym_f)) sym_stiff_t = sum( sym.StiffnessTOperator(d)(f) for d, f in enumerate(sym_f)) sym_k = sym.MassOperator(dq, dd)(sym_kappa * sym_f_quad.dot(sym_normal)) sym_flux = sym.FaceMassOperator()(sym_face_f.dot(sym_face_normal)) # sum everything up sym_op_global = sym.NodalSum(dd)(sym_stiff - (sym_stiff_t + sym_k)) sym_op_local = sym.ElementwiseSumOperator(dd)(sym_stiff - (sym_stiff_t + sym_k + sym_flux)) # evaluate op_global = bind(discr, sym_op_global)(actx) op_local = bind(discr, sym_op_local)(actx) err_global = abs(op_global) err_local = bind(discr, sym.norm(np.inf, sym.var("x")))(actx, x=op_local) logger.info("errors: global %.5e local %.5e", err_global, err_local) # compute max element size h_max = bind( discr, sym.h_max_from_volume(discr.ambient_dim, dim=discr.dim, dd=dd))(actx) eoc_global.add_data_point(h_max, err_global) eoc_local.add_data_point(h_max, err_local) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(discr, vis_order=builder.order) filename = f"surface_divergence_theorem_{mesh_name}_{i:04d}.vtu" vis.write_vtk_file(filename, [("r", actx.np.log10(op_local))], overwrite=True) # }}} order = min(builder.order, builder.mesh_order) - 0.5 logger.info("\n%s", str(eoc_global)) logger.info("\n%s", str(eoc_local)) assert eoc_global.max_error() < 1.0e-12 \ or eoc_global.order_estimate() > order - 0.5 assert eoc_local.max_error() < 1.0e-12 \ or eoc_local.order_estimate() > order - 0.5