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