def test_surface_mass_operator_inverse(actx_factory, name): actx = actx_factory() # {{{ cases if name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() else: raise ValueError("unknown geometry name: %s" % name) # }}} # {{{ convergence from pytools.convergence import EOCRecorder eoc = EOCRecorder() for resolution in builder.resolutions: mesh = builder.get_mesh(resolution, 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) # {{{ compute inverse mass dd = dof_desc.DD_VOLUME sym_f = sym.cos(4.0 * sym.nodes(mesh.ambient_dim, dd)[0]) sym_op = sym.InverseMassOperator(dd, dd)(sym.MassOperator(dd, dd)( sym.var("f"))) f = bind(discr, sym_f)(actx) f_inv = bind(discr, sym_op)(actx, f=f) inv_error = bind( discr, sym.norm(2, sym.var("x") - sym.var("y")) / sym.norm(2, sym.var("y")))( actx, x=f_inv, y=f) # }}} h_max = bind( discr, sym.h_max_from_volume(discr.ambient_dim, dim=discr.dim, dd=dd))(actx) eoc.add_data_point(h_max, inv_error) # }}} logger.info("inverse mass error\n%s", str(eoc)) # NOTE: both cases give 1.0e-16-ish at the moment, but just to be on the # safe side, choose a slightly larger tolerance assert eoc.max_error() < 1.0e-14
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_convergence_advec(ctx_factory, mesh_name, mesh_pars, op_type, flux_type, order, visualize=False): """Test whether 2D advection actually converges""" cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for mesh_par in mesh_pars: if mesh_name == "segment": from meshmode.mesh.generation import generate_box_mesh mesh = generate_box_mesh([np.linspace(-1.0, 1.0, mesh_par)], order=order) dim = 1 dt_factor = 1.0 elif mesh_name == "disk": 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, max_volume=mesh_par) from meshmode.mesh.io import from_meshpy mesh = from_meshpy(mesh_info, order=1) dim = 2 dt_factor = 4 elif mesh_name.startswith("rect"): dim = int(mesh_name[4:]) from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(mesh_par, ) * dim, order=4) if dim == 2: dt_factor = 4 elif dim == 3: dt_factor = 2 else: raise ValueError("dt_factor not known for %dd" % dim) else: raise ValueError("invalid mesh name: " + mesh_name) v = np.array([0.27, 0.31, 0.1])[:dim] norm_v = la.norm(v) def f(x): return sym.sin(10 * x) def u_analytic(x): return f(-v.dot(x) / norm_v + sym.var("t", sym.DD_SCALAR) * norm_v) from grudge.models.advection import (StrongAdvectionOperator, WeakAdvectionOperator) discr = DGDiscretizationWithBoundaries(actx, mesh, order=order) op_class = { "strong": StrongAdvectionOperator, "weak": WeakAdvectionOperator, }[op_type] op = op_class(v, inflow_u=u_analytic(sym.nodes(dim, sym.BTAG_ALL)), flux_type=flux_type) bound_op = bind(discr, op.sym_operator()) u = bind(discr, u_analytic(sym.nodes(dim)))(actx, t=0) def rhs(t, u): return bound_op(t=t, u=u) if dim == 3: final_time = 0.1 else: final_time = 0.2 h_max = bind(discr, sym.h_max_from_volume(discr.ambient_dim))(actx) dt = dt_factor * h_max / order**2 nsteps = (final_time // dt) + 1 dt = final_time / nsteps + 1e-15 from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u, rhs) last_u = None from grudge.shortcuts import make_visualizer vis = make_visualizer(discr, vis_order=order) step = 0 for event in dt_stepper.run(t_end=final_time): if isinstance(event, dt_stepper.StateComputed): step += 1 logger.debug("[%04d] t = %.5f", step, event.t) last_t = event.t last_u = event.state_component if visualize: vis.write_vtk_file("fld-%s-%04d.vtu" % (mesh_par, step), [("u", event.state_component)]) error_l2 = bind(discr, sym.norm(2, sym.var("u") - u_analytic(sym.nodes(dim))))( t=last_t, u=last_u) logger.info("h_max %.5e error %.5e", h_max, error_l2) eoc_rec.add_data_point(h_max, error_l2) logger.info( "\n%s", eoc_rec.pretty_print(abscissa_label="h", error_label="L2 Error")) assert eoc_rec.order_estimate() > order
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 test_mass_surface_area(actx_factory, name): actx = actx_factory() # {{{ cases if name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) surface_area = _ellipse_surface_area(builder.radius, builder.aspect_ratio) elif name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() surface_area = _spheroid_surface_area(builder.radius, builder.aspect_ratio) elif name == "box2d": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=2) surface_area = 1.0 elif name == "box3d": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=3) surface_area = 1.0 else: raise ValueError("unknown geometry name: %s" % name) # }}} # {{{ convergence from pytools.convergence import EOCRecorder eoc = EOCRecorder() for resolution in builder.resolutions: mesh = builder.get_mesh(resolution, 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) # {{{ compute surface area dd = dof_desc.DD_VOLUME sym_op = sym.NodalSum(dd)(sym.MassOperator(dd, dd)(sym.Ones(dd))) approx_surface_area = bind(discr, sym_op)(actx) logger.info("surface: got {:.5e} / expected {:.5e}".format( approx_surface_area, surface_area)) area_error = abs(approx_surface_area - surface_area) / abs(surface_area) # }}} h_max = bind( discr, sym.h_max_from_volume(discr.ambient_dim, dim=discr.dim, dd=dd))(actx) eoc.add_data_point(h_max, area_error + 1.0e-16) # }}} logger.info("surface area error\n%s", str(eoc)) assert eoc.max_error() < 1.0e-14 \ or eoc.order_estimate() > builder.order