def test_norm_obj_array(actx_factory, p): """Test :func:`grudge.op.norm` for object arrays.""" 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=1) dcoll = DiscretizationCollection(actx, mesh, order=4) w = make_obj_array([1.0, 2.0, 3.0])[:dim] # {{ scalar norm = op.norm(dcoll, w[0], p) norm_exact = w[0] logger.info("norm: %.5e %.5e", norm, norm_exact) assert abs(norm - norm_exact) < 1.0e-14 # }}} # {{{ vector norm = op.norm(dcoll, w, p) norm_exact = np.sqrt(np.sum(w**2)) if p == 2 else np.max(w) logger.info("norm: %.5e %.5e", norm, norm_exact) assert abs(norm - norm_exact) < 1.0e-14
def test_mass_operator_inverse(actx_factory, name): actx = actx_factory() # {{{ cases import mesh_data if name == "2-1-ellipse": # curve builder = mesh_data.EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif name == "spheroid": # surface builder = mesh_data.SpheroidMeshBuilder() elif name.startswith("warped_rect"): builder = mesh_data.WarpedRectMeshBuilder(dim=int(name[-1])) else: raise ValueError("unknown geometry name: %s" % name) # }}} # {{{ inv(m) @ m == id from pytools.convergence import EOCRecorder eoc = EOCRecorder() for resolution in builder.resolutions: mesh = builder.get_mesh(resolution, builder.mesh_order) dcoll = DiscretizationCollection(actx, mesh, order=builder.order) 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) # {{{ compute inverse mass def f(x): return actx.np.cos(4.0 * x[0]) dd = dof_desc.DD_VOLUME x_volm = thaw(volume_discr.nodes(), actx) f_volm = f(x_volm) f_inv = op.inverse_mass(dcoll, op.mass(dcoll, dd, f_volm)) inv_error = actx.to_numpy( op.norm(dcoll, f_volm - f_inv, 2) / op.norm(dcoll, f_volm, 2)) # }}} # compute max element size from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(dcoll) 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 test_trace_pair(actx_factory): """Simple smoke test for :class:`grudge.trace_pair.TracePair`.""" actx = actx_factory() dim = 3 order = 1 n = 4 mesh = mgen.generate_regular_rect_mesh( a=(-1,)*dim, b=(1,)*dim, nelements_per_axis=(n,)*dim) dcoll = DiscretizationCollection(actx, mesh, order=order) def rand(): return DOFArray( actx, tuple(actx.from_numpy( np.random.rand(grp.nelements, grp.nunit_dofs)) for grp in dcoll.discr_from_dd("vol").groups)) interior = rand() exterior = rand() tpair = TracePair("vol", interior=interior, exterior=exterior) import grudge.op as op assert op.norm(dcoll, tpair.avg - 0.5*(exterior + interior), np.inf) == 0 assert op.norm(dcoll, tpair.diff - (exterior - interior), np.inf) == 0 assert op.norm(dcoll, tpair.int - interior, np.inf) == 0 assert op.norm(dcoll, tpair.ext - exterior, np.inf) == 0
def test_bessel(actx_factory): actx = actx_factory() dims = 2 mesh = mgen.generate_regular_rect_mesh(a=(0.1, ) * dims, b=(1.0, ) * dims, nelements_per_axis=(8, ) * dims) dcoll = DiscretizationCollection(actx, mesh, order=3) nodes = thaw(dcoll.nodes(), actx) r = actx.np.sqrt(nodes[0]**2 + nodes[1]**2) # FIXME: Bessel functions need to brought out of the symbolic # layer. Related issue: https://github.com/inducer/grudge/issues/93 def bessel_j(actx, n, r): from grudge import sym, bind return bind(dcoll, sym.bessel_j(n, sym.var("r")))(actx, r=r) # https://dlmf.nist.gov/10.6.1 n = 3 bessel_zero = (bessel_j(actx, n + 1, r) + bessel_j(actx, n - 1, r) - 2 * n / r * bessel_j(actx, n, r)) z = op.norm(dcoll, bessel_zero, 2) assert z < 1e-15
def conv_test(descr, use_quad): logger.info("-" * 75) logger.info(descr) logger.info("-" * 75) eoc_rec = EOCRecorder() if use_quad: qtag = dof_desc.DISCR_TAG_QUAD else: qtag = None ns = [20, 25] for n in ns: mesh = mgen.generate_regular_rect_mesh(a=(-0.5, ) * dims, b=(0.5, ) * dims, nelements_per_axis=(n, ) * dims, order=order) if use_quad: discr_tag_to_group_factory = { qtag: QuadratureSimplexGroupFactory(order=4 * order) } else: discr_tag_to_group_factory = {} dcoll = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory) nodes = thaw(dcoll.nodes(), actx) def zero_inflow(dtag, t=0): dd = dof_desc.DOFDesc(dtag, qtag) return dcoll.discr_from_dd(dd).zeros(actx) adv_op = VariableCoefficientAdvectionOperator( dcoll, flat_obj_array(-1 * nodes[1], nodes[0]), inflow_u=lambda t: zero_inflow(BTAG_ALL, t=t), flux_type="upwind", quad_tag=qtag) total_error = op.norm(dcoll, adv_op.operator(0, gaussian_mode(nodes)), 2) eoc_rec.add_data_point(1.0 / n, actx.to_numpy(total_error)) logger.info( "\n%s", eoc_rec.pretty_print(abscissa_label="h", error_label="L2 Error")) return eoc_rec.order_estimate(), np.array( [x[1] for x in eoc_rec.history])
def test_elementwise_reductions(actx_factory): actx = actx_factory() from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=1) nelements = 4 mesh = builder.get_mesh(nelements, builder.mesh_order) dcoll = DiscretizationCollection(actx, mesh, order=builder.order) x = thaw(dcoll.nodes(), actx) def f(x): return actx.np.sin(x[0]) field = f(x) mins = [] maxs = [] sums = [] for grp_f in field: min_res = np.empty(grp_f.shape) max_res = np.empty(grp_f.shape) sum_res = np.empty(grp_f.shape) for eidx in range(dcoll.mesh.nelements): element_data = actx.to_numpy(grp_f[eidx]) min_res[eidx, :] = np.min(element_data) max_res[eidx, :] = np.max(element_data) sum_res[eidx, :] = np.sum(element_data) mins.append(actx.from_numpy(min_res)) maxs.append(actx.from_numpy(max_res)) sums.append(actx.from_numpy(sum_res)) ref_mins = DOFArray(actx, data=tuple(mins)) ref_maxs = DOFArray(actx, data=tuple(maxs)) ref_sums = DOFArray(actx, data=tuple(sums)) elem_mins = op.elementwise_min(dcoll, field) elem_maxs = op.elementwise_max(dcoll, field) elem_sums = op.elementwise_sum(dcoll, field) assert actx.to_numpy(op.norm(dcoll, elem_mins - ref_mins, np.inf)) < 1.e-15 assert actx.to_numpy(op.norm(dcoll, elem_maxs - ref_maxs, np.inf)) < 1.e-15 assert actx.to_numpy(op.norm(dcoll, elem_sums - ref_sums, np.inf)) < 1.e-15
def test_nodal_reductions_with_container(actx_factory): actx = actx_factory() from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=2) mesh = builder.get_mesh(4, builder.mesh_order) dcoll = DiscretizationCollection(actx, mesh, order=builder.order) x = thaw(dcoll.nodes(), actx) def f(x): return -actx.np.sin(10 * x[0]) * actx.np.cos(2 * x[1]) def g(x): return actx.np.cos(2 * x[0]) * actx.np.sin(10 * x[1]) def h(x): return -actx.np.tan(5 * x[0]) * actx.np.tan(0.5 * x[1]) mass = f(x) + g(x) momentum = make_obj_array([f(x) / g(x), h(x)]) enthalpy = h(x) - g(x) ary_container = MyContainer(name="container", mass=mass, momentum=momentum, enthalpy=enthalpy) mass_ref = actx.to_numpy(flatten(mass)) momentum_ref = np.concatenate( [actx.to_numpy(mom_i) for mom_i in flatten(momentum)]) enthalpy_ref = actx.to_numpy(flatten(enthalpy)) concat_fields = np.concatenate([mass_ref, momentum_ref, enthalpy_ref]) for grudge_op, np_op in [(op.nodal_sum, np.sum), (op.nodal_max, np.max), (op.nodal_min, np.min)]: assert np.isclose(actx.to_numpy(grudge_op(dcoll, "vol", ary_container)), np_op(concat_fields), rtol=1e-13) # Check norm reduction assert np.isclose(actx.to_numpy(op.norm(dcoll, ary_container, np.inf)), np.linalg.norm(concat_fields, ord=np.inf), rtol=1e-13)
def test_norm_complex(actx_factory, p): actx = actx_factory() dim = 2 mesh = mgen.generate_regular_rect_mesh(a=(0, ) * dim, b=(1, ) * dim, nelements_per_axis=(8, ) * dim, order=1) dcoll = DiscretizationCollection(actx, mesh, order=4) nodes = thaw(dcoll.nodes(), actx) norm = op.norm(dcoll, (1 + 1j) * nodes[0], p) if p == 2: ref_norm = (2 / 3)**0.5 elif p == np.inf: ref_norm = 2**0.5 logger.info("norm: %.5e %.5e", norm, ref_norm) assert abs(norm - ref_norm) / abs(ref_norm) < 1e-13
def test_convergence_advec(actx_factory, mesh_name, mesh_pars, op_type, flux_type, order, visualize=False): """Test whether 2D advection actually converges""" actx = actx_factory() from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for mesh_par in mesh_pars: if mesh_name == "segment": mesh = mgen.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[-1:]) mesh = mgen.generate_regular_rect_mesh( a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(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) elif mesh_name.startswith("warped"): dim = int(mesh_name[-1:]) mesh = mgen.generate_warped_rect_mesh(dim, order=order, nelements_side=mesh_par) 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 actx.np.sin(10 * x) def u_analytic(x, t=0): return f(-v.dot(x) / norm_v + t * norm_v) from grudge.models.advection import (StrongAdvectionOperator, WeakAdvectionOperator) from meshmode.mesh import BTAG_ALL dcoll = DiscretizationCollection(actx, mesh, order=order) op_class = { "strong": StrongAdvectionOperator, "weak": WeakAdvectionOperator }[op_type] adv_operator = op_class(dcoll, v, inflow_u=lambda t: u_analytic( thaw(dcoll.nodes(dd=BTAG_ALL), actx), t=t), flux_type=flux_type) nodes = thaw(dcoll.nodes(), actx) u = u_analytic(nodes, t=0) def rhs(t, u): return adv_operator.operator(t, u) if dim == 3: final_time = 0.1 else: final_time = 0.2 from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(dcoll, dim=dcoll.ambient_dim) 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(dcoll) 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 = op.norm(dcoll, last_u - u_analytic(nodes, t=last_t), 2) logger.info("h_max %.5e error %.5e", h_max, error_l2) eoc_rec.add_data_point(h_max, actx.to_numpy(error_l2)) logger.info( "\n%s", eoc_rec.pretty_print(abscissa_label="h", error_label="L2 Error")) if mesh_name.startswith("warped"): # NOTE: curvilinear meshes are hard assert eoc_rec.order_estimate() > order - 0.5 else: assert eoc_rec.order_estimate() > order
def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) comm = MPI.COMM_WORLD num_parts = comm.Get_size() from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() order = 3 dcoll = DiscretizationCollection(actx, local_mesh, order=order, mpi_communicator=comm) if dim == 2: # no deep meaning here, just a fudge factor dt = 0.75 / (nel_1d * order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.45 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") fields = flat_obj_array(bump(actx, dcoll), [dcoll.zeros(actx) for i in range(dcoll.dim)]) vis = make_visualizer(dcoll) def rhs(t, w): return wave_operator(dcoll, c=1, w=w) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: print(istep, t, op.norm(dcoll, fields[0], p=2)) vis.write_parallel_vtk_file( comm, f"fld-wave-eager-mpi-{{rank:03d}}-{istep:04d}.vtu", [ ("u", fields[0]), ("v", fields[1:]), ]) t += dt istep += 1
def main(ctx_factory, dim=2, order=4, use_quad=False, visualize=False, flux_type="upwind"): 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 # domain [0, d]^dim d = 1.0 # number of points in each dimension npoints = 25 # final time final_time = 1 if use_quad: qtag = dof_desc.DISCR_TAG_QUAD else: qtag = None # }}} # {{{ discretization from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(0, ) * dim, b=(d, ) * dim, npoints_per_axis=(npoints, ) * dim, order=order) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory if use_quad: discr_tag_to_group_factory = { qtag: QuadratureSimplexGroupFactory(order=4 * order) } else: discr_tag_to_group_factory = {} from grudge import DiscretizationCollection dcoll = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory) # }}} # {{{ advection operator # gaussian parameters def f_halfcircle(x): source_center = np.array([d / 2, d / 2, d / 2])[:dim] dist = x - source_center return ((0.5 + 0.5 * actx.np.tanh(500 * (-np.dot(dist, dist) + 0.4**2))) * (0.5 + 0.5 * actx.np.tanh(500 * (dist[0])))) def zero_inflow_bc(dtag, t=0): dd = dof_desc.DOFDesc(dtag, qtag) return dcoll.discr_from_dd(dd).zeros(actx) from grudge.models.advection import VariableCoefficientAdvectionOperator x = thaw(dcoll.nodes(), actx) # velocity field if dim == 1: c = x else: # solid body rotation c = flat_obj_array(np.pi * (d / 2 - x[1]), np.pi * (x[0] - d / 2), 0)[:dim] adv_operator = VariableCoefficientAdvectionOperator( dcoll, c, inflow_u=lambda t: zero_inflow_bc(BTAG_ALL, t), quad_tag=qtag, flux_type=flux_type) u = f_halfcircle(x) def rhs(t, u): return adv_operator.operator(t, u) dt = actx.to_numpy( adv_operator.estimate_rk4_timestep(actx, dcoll, fields=u)) logger.info("Timestep size: %g", dt) # }}} # {{{ time stepping from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u, rhs) plot = Plotter(actx, dcoll, order, visualize=visualize, ylim=[-0.1, 1.1]) step = 0 for event in dt_stepper.run(t_end=final_time): if not isinstance(event, dt_stepper.StateComputed): continue if step % 10 == 0: norm_u = actx.to_numpy(op.norm(dcoll, event.state_component, 2)) plot(event, "fld-var-velocity-%04d" % step) step += 1 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 < 1
FACE_RESTR_INTERIOR, "all_faces", flux(dcoll, interior_tpair))) duh_by_dt = op.inverse_mass(dcoll, np.dot([2 * np.pi], Su) - lift) # forward euler time step uh = uh + dt * duh_by_dt t += dt # ENDEXAMPLE # Plot the solution: def u_exact(x, t): return actx.np.sin(x[0] - 2 * np.pi * t) assert op.norm(dcoll, uh - u_exact(x_vol, t_final), p=2) <= 0.1 import matplotlib.pyplot as plt from arraycontext import to_numpy plt.plot(to_numpy(actx.np.ravel(x_vol[0][0]), actx), to_numpy(actx.np.ravel(uh[0]), actx), label="Numerical") plt.plot(to_numpy(actx.np.ravel(x_vol[0][0]), actx), to_numpy(actx.np.ravel(u_exact(x_vol, t_final)[0]), actx), label="Exact") plt.xlabel("$x$") plt.ylabel("$u$") plt.legend() plt.show()
def main(ctx_factory, dim=2, order=3, 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, ) nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(-0.5,)*dim, b=(0.5,)*dim, nelements_per_axis=(nel_1d,)*dim) logger.info("%d elements", mesh.nelements) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory, \ default_simplex_group_factory dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory(base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(3*order), } ) # bounded above by 1 c = 0.2 + 0.8*bump(actx, dcoll, center=np.zeros(3), width=0.5) dt = 0.5 * estimate_rk4_timestep(actx, dcoll, c=1) fields = flat_obj_array( bump(actx, dcoll, ), [dcoll.zeros(actx) for i in range(dcoll.dim)] ) vis = make_visualizer(dcoll) def rhs(t, w): return wave_operator(dcoll, c=c, w=w) logger.info("dt = %g", dt) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: logger.info(f"step: {istep} t: {t} " f"L2: {op.norm(dcoll, fields[0], 2)} " f"Linf: {op.norm(dcoll, fields[0], np.inf)} " f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])} " f"sol min: {op.nodal_min(dcoll, 'vol', fields[0])}") if visualize: vis.write_vtk_file( f"fld-wave-eager-var-velocity-{istep:04d}.vtu", [ ("c", c), ("u", fields[0]), ("v", fields[1:]), ] ) t += dt istep += 1 # NOTE: These are here to ensure the solution is bounded for the # time interval specified assert op.norm(dcoll, fields[0], 2) < 1
def norm(self, vec, p=2, dd=None): return op.norm(self, vec, p, dd)
def main(ctx_factory, dim=2, order=3, visualize=False, lazy=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) if lazy: actx = PytatoPyOpenCLArrayContext(queue) else: actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), force_device_scalars=True, ) comm = MPI.COMM_WORLD num_parts = comm.Get_size() from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) nel_1d = 16 if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) logger.info("%d elements", mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() dcoll = DiscretizationCollection(actx, local_mesh, order=order, mpi_communicator=comm) fields = WaveState(u=bump(actx, dcoll), v=make_obj_array( [dcoll.zeros(actx) for i in range(dcoll.dim)])) c = 1 dt = actx.to_numpy(0.45 * estimate_rk4_timestep(actx, dcoll, c)) vis = make_visualizer(dcoll) def rhs(t, w): return wave_operator(dcoll, c=c, w=w) compiled_rhs = actx.compile(rhs) if comm.rank == 0: logger.info("dt = %g", dt) import time start = time.time() t = 0 t_final = 3 istep = 0 while t < t_final: if lazy: fields = thaw(freeze(fields, actx), actx) fields = rk4_step(fields, t, dt, compiled_rhs) l2norm = actx.to_numpy(op.norm(dcoll, fields.u, 2)) if istep % 10 == 0: stop = time.time() linfnorm = actx.to_numpy(op.norm(dcoll, fields.u, np.inf)) nodalmax = actx.to_numpy(op.nodal_max(dcoll, "vol", fields.u)) nodalmin = actx.to_numpy(op.nodal_min(dcoll, "vol", fields.u)) if comm.rank == 0: logger.info(f"step: {istep} t: {t} " f"L2: {l2norm} " f"Linf: {linfnorm} " f"sol max: {nodalmax} " f"sol min: {nodalmin} " f"wall: {stop-start} ") if visualize: vis.write_parallel_vtk_file( comm, f"fld-wave-eager-mpi-{{rank:03d}}-{istep:04d}.vtu", [ ("u", fields.u), ("v", fields.v), ]) start = stop t += dt istep += 1 # NOTE: These are here to ensure the solution is bounded for the # time interval specified assert l2norm < 1
def test_elementwise_reductions_with_container(actx_factory): actx = actx_factory() from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=2) nelements = 4 mesh = builder.get_mesh(nelements, builder.mesh_order) dcoll = DiscretizationCollection(actx, mesh, order=builder.order) x = thaw(dcoll.nodes(), actx) def f(x): return actx.np.sin(x[0]) * actx.np.sin(x[1]) def g(x): return actx.np.cos(x[0]) * actx.np.cos(x[1]) def h(x): return actx.np.cos(x[0]) * actx.np.sin(x[1]) mass = 2 * f(x) + 0.5 * g(x) momentum = make_obj_array([f(x) / g(x), h(x)]) enthalpy = 3 * h(x) - g(x) ary_container = MyContainer(name="container", mass=mass, momentum=momentum, enthalpy=enthalpy) def _get_ref_data(field): mins = [] maxs = [] sums = [] for grp_f in field: min_res = np.empty(grp_f.shape) max_res = np.empty(grp_f.shape) sum_res = np.empty(grp_f.shape) for eidx in range(dcoll.mesh.nelements): element_data = actx.to_numpy(grp_f[eidx]) min_res[eidx, :] = np.min(element_data) max_res[eidx, :] = np.max(element_data) sum_res[eidx, :] = np.sum(element_data) mins.append(actx.from_numpy(min_res)) maxs.append(actx.from_numpy(max_res)) sums.append(actx.from_numpy(sum_res)) min_field = DOFArray(actx, data=tuple(mins)) max_field = DOFArray(actx, data=tuple(maxs)) sums_field = DOFArray(actx, data=tuple(sums)) return min_field, max_field, sums_field min_mass, max_mass, sums_mass = _get_ref_data(mass) min_enthalpy, max_enthalpy, sums_enthalpy = _get_ref_data(enthalpy) min_mom_x, max_mom_x, sums_mom_x = _get_ref_data(momentum[0]) min_mom_y, max_mom_y, sums_mom_y = _get_ref_data(momentum[1]) min_momentum = make_obj_array([min_mom_x, min_mom_y]) max_momentum = make_obj_array([max_mom_x, max_mom_y]) sums_momentum = make_obj_array([sums_mom_x, sums_mom_y]) reference_min = MyContainer(name="Reference min", mass=min_mass, momentum=min_momentum, enthalpy=min_enthalpy) reference_max = MyContainer(name="Reference max", mass=max_mass, momentum=max_momentum, enthalpy=max_enthalpy) reference_sum = MyContainer(name="Reference sums", mass=sums_mass, momentum=sums_momentum, enthalpy=sums_enthalpy) elem_mins = op.elementwise_min(dcoll, ary_container) elem_maxs = op.elementwise_max(dcoll, ary_container) elem_sums = op.elementwise_sum(dcoll, ary_container) assert actx.to_numpy(op.norm(dcoll, elem_mins - reference_min, np.inf)) < 1.e-14 assert actx.to_numpy(op.norm(dcoll, elem_maxs - reference_max, np.inf)) < 1.e-14 assert actx.to_numpy(op.norm(dcoll, elem_sums - reference_sum, np.inf)) < 1.e-14
def main(ctx_factory, dim=2, order=4, 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 # domain [-d/2, d/2]^dim d = 1.0 # number of points in each dimension npoints = 20 # final time final_time = 1.0 # velocity field c = np.array([0.5] * dim) norm_c = la.norm(c) # flux flux_type = "central" # }}} # {{{ discretization from meshmode.mesh.generation import generate_box_mesh mesh = generate_box_mesh( [np.linspace(-d / 2, d / 2, npoints) for _ in range(dim)], order=order) from grudge import DiscretizationCollection dcoll = DiscretizationCollection(actx, mesh, order=order) # }}} # {{{ weak advection operator def f(x): return actx.np.sin(3 * x) def u_analytic(x, t=0): return f(-np.dot(c, x) / norm_c + t * norm_c) from grudge.models.advection import WeakAdvectionOperator adv_operator = WeakAdvectionOperator( dcoll, c, inflow_u=lambda t: u_analytic(thaw(dcoll.nodes(dd=BTAG_ALL), actx), t=t), flux_type=flux_type) nodes = thaw(dcoll.nodes(), actx) u = u_analytic(nodes, t=0) def rhs(t, u): return adv_operator.operator(t, u) dt = actx.to_numpy( adv_operator.estimate_rk4_timestep(actx, dcoll, fields=u)) logger.info("Timestep size: %g", dt) # }}} # {{{ time stepping from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u, rhs) plot = Plotter(actx, dcoll, order, visualize=visualize, ylim=[-1.1, 1.1]) step = 0 norm_u = 0.0 for event in dt_stepper.run(t_end=final_time): if not isinstance(event, dt_stepper.StateComputed): continue if step % 10 == 0: norm_u = actx.to_numpy(op.norm(dcoll, event.state_component, 2)) plot(event, "fld-weak-%04d" % step) step += 1 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 < 1
def test_gradient(actx_factory, form, dim, order, vectorize, nested, visualize=False): actx = actx_factory() from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for n in [4, 6, 8]: mesh = mgen.generate_regular_rect_mesh(a=(-1, ) * dim, b=(1, ) * dim, nelements_per_axis=(n, ) * dim) dcoll = DiscretizationCollection(actx, mesh, order=order) def f(x): result = dcoll.zeros(actx) + 1 for i in range(dim - 1): result = result * actx.np.sin(np.pi * x[i]) result = result * actx.np.cos(np.pi / 2 * x[dim - 1]) return result def grad_f(x): result = make_obj_array( [dcoll.zeros(actx) + 1 for _ in range(dim)]) for i in range(dim - 1): for j in range(i): result[i] = result[i] * actx.np.sin(np.pi * x[j]) result[i] = result[i] * np.pi * actx.np.cos(np.pi * x[i]) for j in range(i + 1, dim - 1): result[i] = result[i] * actx.np.sin(np.pi * x[j]) result[i] = result[i] * actx.np.cos(np.pi / 2 * x[dim - 1]) for j in range(dim - 1): result[dim - 1] = result[dim - 1] * actx.np.sin(np.pi * x[j]) result[dim - 1] = result[dim - 1] * (-np.pi / 2 * actx.np.sin(np.pi / 2 * x[dim - 1])) return result x = thaw(dcoll.nodes(), actx) if vectorize: u = make_obj_array([(i + 1) * f(x) for i in range(dim)]) else: u = f(x) def get_flux(u_tpair): dd = u_tpair.dd dd_allfaces = dd.with_dtag("all_faces") normal = thaw(dcoll.normal(dd), actx) u_avg = u_tpair.avg if vectorize: if nested: flux = make_obj_array( [u_avg_i * normal for u_avg_i in u_avg]) else: flux = np.outer(u_avg, normal) else: flux = u_avg * normal return op.project(dcoll, dd, dd_allfaces, flux) dd_allfaces = DOFDesc("all_faces") if form == "strong": grad_u = ( op.local_grad(dcoll, u, nested=nested) # No flux terms because u doesn't have inter-el jumps ) elif form == "weak": grad_u = op.inverse_mass( dcoll, -op.weak_local_grad(dcoll, u, nested=nested) # pylint: disable=E1130 + # noqa: W504 op.face_mass( dcoll, dd_allfaces, # Note: no boundary flux terms here because u_ext == u_int == 0 sum( get_flux(utpair) for utpair in op.interior_trace_pairs(dcoll, u)))) else: raise ValueError("Invalid form argument.") if vectorize: expected_grad_u = make_obj_array([(i + 1) * grad_f(x) for i in range(dim)]) if not nested: expected_grad_u = np.stack(expected_grad_u, axis=0) else: expected_grad_u = grad_f(x) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll, vis_order=order if dim == 3 else dim + 3) filename = ( f"test_gradient_{form}_{dim}_{order}" f"{'_vec' if vectorize else ''}{'_nested' if nested else ''}.vtu" ) vis.write_vtk_file(filename, [ ("u", u), ("grad_u", grad_u), ("expected_grad_u", expected_grad_u), ], overwrite=True) rel_linf_err = actx.to_numpy( op.norm(dcoll, grad_u - expected_grad_u, np.inf) / op.norm(dcoll, expected_grad_u, np.inf)) eoc_rec.add_data_point(1. / n, rel_linf_err) print("L^inf error:") print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11)
def norm(u): return op.norm(dcoll, u, 2)
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) # }}} # {{{ convergence def f(x): return flat_obj_array( actx.np.sin(3 * x[1]) + actx.np.cos(3 * x[0]) + 1.0, actx.np.sin(2 * x[0]) + actx.np.cos(x[1]), 3.0 * actx.np.cos(x[0] / 2) + actx.np.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 qtag = dof_desc.DISCR_TAG_QUAD dcoll = DiscretizationCollection(actx, mesh, order=builder.order, discr_tag_to_group_factory={ qtag: QuadratureSimplexGroupFactory( 2 * builder.order) }) volume = dcoll.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(qtag) df = dof_desc.as_dofdesc(FACE_RESTR_ALL) ambient_dim = dcoll.ambient_dim # variables f_num = f(thaw(dcoll.nodes(dd=dd), actx)) f_quad_num = f(thaw(dcoll.nodes(dd=dq), actx)) from grudge.geometry import normal, summed_curvature kappa = summed_curvature(actx, dcoll, dd=dq) normal = normal(actx, dcoll, dd=dq) face_normal = thaw(dcoll.normal(df), actx) face_f = op.project(dcoll, dd, df, f_num) # operators stiff = op.mass( dcoll, sum( op.local_d_dx(dcoll, i, f_num_i) for i, f_num_i in enumerate(f_num))) stiff_t = sum( op.weak_local_d_dx(dcoll, i, f_num_i) for i, f_num_i in enumerate(f_num)) kterm = op.mass(dcoll, dq, kappa * f_quad_num.dot(normal)) flux = op.face_mass(dcoll, face_f.dot(face_normal)) # sum everything up op_global = op.nodal_sum(dcoll, dd, stiff - (stiff_t + kterm)) op_local = op.elementwise_sum(dcoll, dd, stiff - (stiff_t + kterm + flux)) err_global = abs(op_global) err_local = op.norm(dcoll, op_local, np.inf) logger.info("errors: global %.5e local %.5e", err_global, err_local) # compute max element size from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(dcoll) eoc_global.add_data_point(h_max, actx.to_numpy(err_global)) eoc_local.add_data_point(h_max, err_local) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll) 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 _eval_error(x): return op.norm(dcoll, x, np.inf, dd=df)
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 main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 2 nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 3 if dim == 2: # no deep meaning here, just a fudge factor dt = 0.75 / (nel_1d * order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.45 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") print("%d elements" % mesh.nelements) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory, \ PolynomialWarpAndBlendGroupFactory dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: PolynomialWarpAndBlendGroupFactory(order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(3 * order), }) # bounded above by 1 c = 0.2 + 0.8 * bump(actx, dcoll, center=np.zeros(3), width=0.5) fields = flat_obj_array(bump( actx, dcoll, ), [dcoll.zeros(actx) for i in range(dcoll.dim)]) vis = make_visualizer(dcoll) def rhs(t, w): return wave_operator(dcoll, c=c, w=w) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: print(istep, t, op.norm(dcoll, fields[0], p=2)) vis.write_vtk_file("fld-wave-eager-var-velocity-%04d.vtu" % istep, [ ("c", c), ("u", fields[0]), ("v", fields[1:]), ]) t += dt istep += 1
def test_convergence_maxwell(actx_factory, order): """Test whether 3D Maxwell's actually converges""" actx = actx_factory() from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() dims = 3 ns = [4, 6, 8] for n in ns: mesh = mgen.generate_regular_rect_mesh(a=(0.0, ) * dims, b=(1.0, ) * dims, nelements_per_axis=(n, ) * dims) dcoll = DiscretizationCollection(actx, mesh, order=order) epsilon = 1 mu = 1 from grudge.models.em import get_rectangular_cavity_mode def analytic_sol(x, t=0): return get_rectangular_cavity_mode(actx, x, t, 1, (1, 2, 2)) nodes = thaw(dcoll.nodes(), actx) fields = analytic_sol(nodes, t=0) from grudge.models.em import MaxwellOperator maxwell_operator = MaxwellOperator(dcoll, epsilon, mu, flux_type=0.5, dimensions=dims) maxwell_operator.check_bc_coverage(mesh) def rhs(t, w): return maxwell_operator.operator(t, w) dt = maxwell_operator.estimate_rk4_timestep(actx, dcoll) final_t = dt * 5 nsteps = int(final_t / dt) from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("w", dt, fields, rhs) logger.info("dt %.5e nsteps %5d", dt, nsteps) step = 0 for event in dt_stepper.run(t_end=final_t): if isinstance(event, dt_stepper.StateComputed): assert event.component_id == "w" esc = event.state_component step += 1 logger.debug("[%04d] t = %.5e", step, event.t) sol = analytic_sol(nodes, t=step * dt) total_error = op.norm(dcoll, esc - sol, 2) eoc_rec.add_data_point(1.0 / n, actx.to_numpy(total_error)) logger.info( "\n%s", eoc_rec.pretty_print(abscissa_label="h", error_label="L2 Error")) assert eoc_rec.order_estimate() > order