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 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_isentropic_vortex(actx_factory, order): """Advance the 2D isentropic vortex case in time with non-zero velocities using an RK4 timestepping scheme. Check the advanced field values against the exact/analytic expressions. This tests all parts of the Euler module working together, with results converging at the expected rates vs. the order. """ actx = actx_factory() dim = 2 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for nel_1d in [16, 32, 64]: from meshmode.mesh.generation import ( generate_regular_rect_mesh, ) mesh = generate_regular_rect_mesh(a=(-5.0, ) * dim, b=(5.0, ) * dim, nelements_per_axis=(nel_1d, ) * dim) exittol = 1.0 t_final = 0.001 cfl = 1.0 vel = np.zeros(shape=(dim, )) orig = np.zeros(shape=(dim, )) vel[:dim] = 1.0 dt = .0001 initializer = Vortex2D(center=orig, velocity=vel) casename = "Vortex" boundaries = {BTAG_ALL: PrescribedBoundary(initializer)} eos = IdealSingleGas() t = 0 flowparams = { "dim": dim, "dt": dt, "order": order, "time": t, "boundaries": boundaries, "initializer": initializer, "eos": eos, "casename": casename, "mesh": mesh, "tfinal": t_final, "exittol": exittol, "cfl": cfl, "constantcfl": False, "nstatus": 0 } maxerr = _euler_flow_stepper(actx, flowparams) eoc_rec.add_data_point(1.0 / nel_1d, maxerr) logger.info(f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}") assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11)
def test_wave_accuracy(actx_factory, problem, order, visualize=False): """Checks accuracy of the wave operator for a given problem setup. """ actx = actx_factory() p = problem sym_u, sym_v, sym_f, sym_rhs = sym_wave(p.dim, p.sym_phi) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for n in [8, 10, 12] if p.dim == 3 else [8, 12, 16]: mesh = p.mesh_factory(n) from grudge.eager import EagerDGDiscretization discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) def sym_eval(expr, t): return sym.EvaluationMapper({"c": p.c, "x": nodes, "t": t})(expr) t_check = 1.23456789 u = sym_eval(sym_u, t_check) v = sym_eval(sym_v, t_check) fields = flat_obj_array(u, v) rhs = wave_operator(discr, c=p.c, w=fields) rhs[0] = rhs[0] + sym_eval(sym_f, t_check) expected_rhs = sym_eval(sym_rhs, t_check) rel_linf_err = actx.to_numpy( discr.norm(rhs - expected_rhs, np.inf) / discr.norm(expected_rhs, np.inf)) eoc_rec.add_data_point(1. / n, rel_linf_err) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(discr, discr.order) vis.write_vtk_file( "wave_accuracy_{order}_{n}.vtu".format(order=order, n=n), [ ("u", fields[0]), ("v", fields[1:]), ("rhs_u_actual", rhs[0]), ("rhs_v_actual", rhs[1:]), ("rhs_u_expected", expected_rhs[0]), ("rhs_v_expected", expected_rhs[1:]), ]) print("Approximation error:") print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11)
def test_lump_rhs(actx_factory, dim, order): """Test the inviscid rhs using the non-trivial mass lump case. The case is tested against the analytic expressions of the RHS. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() tolerance = 1e-10 maxxerr = 0.0 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for nel_1d in [4, 8, 12]: from meshmode.mesh.generation import ( generate_regular_rect_mesh, ) mesh = generate_regular_rect_mesh( a=(-5, ) * dim, b=(5, ) * dim, nelements_per_axis=(nel_1d, ) * dim, ) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) # Init soln with Lump and expected RHS = 0 center = np.zeros(shape=(dim, )) velocity = np.zeros(shape=(dim, )) lump = Lump(dim=dim, center=center, velocity=velocity) lump_soln = lump(nodes) boundaries = { BTAG_ALL: PrescribedInviscidBoundary(fluid_solution_func=lump) } inviscid_rhs = euler_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, cv=lump_soln, time=0.0) expected_rhs = lump.exact_rhs(discr, cv=lump_soln, time=0) err_max = discr.norm((inviscid_rhs - expected_rhs).join(), np.inf) if err_max > maxxerr: maxxerr = err_max eoc_rec.add_data_point(1.0 / nel_1d, err_max) logger.info(f"Max error: {maxxerr}") logger.info(f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}") assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < tolerance)
def test_velocity_gradient_eoc(actx_factory, dim): """Test that the velocity gradient converges at the proper rate.""" from mirgecom.fluid import velocity_gradient actx = actx_factory() order = 3 from pytools.convergence import EOCRecorder eoc = EOCRecorder() nel_1d_0 = 4 for hn1 in [1, 2, 3, 4]: nel_1d = hn1 * nel_1d_0 h = 1/nel_1d from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(1.0,) * dim, b=(2.0,) * dim, nelements_per_axis=(nel_1d,) * dim ) discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) energy = zeros + 2.5 mass = nodes[dim-1]*nodes[dim-1] velocity = make_obj_array([actx.np.cos(nodes[i]) for i in range(dim)]) mom = mass*velocity q = join_conserved(dim, mass=mass, energy=energy, momentum=mom) cv = split_conserved(dim, q) grad_q = obj_array_vectorize(discr.grad, q) grad_cv = split_conserved(dim, grad_q) grad_v = velocity_gradient(discr, cv, grad_cv) def exact_grad_row(xdata, gdim, dim): exact_grad_row = make_obj_array([zeros for _ in range(dim)]) exact_grad_row[gdim] = -actx.np.sin(xdata) return exact_grad_row comp_err = make_obj_array([ discr.norm(grad_v[i] - exact_grad_row(nodes[i], i, dim), np.inf) for i in range(dim)]) err_max = comp_err.max() eoc.add_data_point(h, err_max) logger.info(eoc) assert ( eoc.order_estimate() >= order - 0.5 or eoc.max_error() < 1e-9 )
def test_basis_grad(dim, shape_cls, order, basis_getter): """Do a simplistic FD-style check on the gradients of the basis.""" h = 1.0e-4 shape = shape_cls(dim) rng = np.random.Generator(np.random.PCG64(17)) basis = basis_getter(mp.space_for_shape(shape, order), shape) from pytools.convergence import EOCRecorder from pytools import wandering_element for i_bf, (bf, gradbf) in enumerate(zip( basis.functions, basis.gradients, )): eoc_rec = EOCRecorder() for h in [1e-2, 1e-3]: r = mp.random_nodes_for_shape(shape, nnodes=1000, rng=rng) gradbf_v = np.array(gradbf(r)) gradbf_v_num = np.array([ (bf(r + h * unit) - bf(r - h * unit)) / (2 * h) for unit_tuple in wandering_element(shape.dim) for unit in (np.array(unit_tuple).reshape(-1, 1), ) ]) ref_norm = la.norm((gradbf_v).reshape(-1), np.inf) err = la.norm((gradbf_v_num - gradbf_v).reshape(-1), np.inf) if ref_norm > 1e-13: err = err / ref_norm logger.info("error: %.5", err) eoc_rec.add_data_point(h, err) tol = 1e-8 if eoc_rec.max_error() >= tol: print(eoc_rec) assert (eoc_rec.max_error() < tol or eoc_rec.order_estimate() >= 1.5)
def test_vortex_rhs(actx_factory, order): """Tests the inviscid rhs using the non-trivial 2D isentropic vortex case configured to yield rhs = 0. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() dim = 2 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() from meshmode.mesh.generation import generate_regular_rect_mesh for nel_1d in [16, 32, 64]: mesh = generate_regular_rect_mesh( a=(-5, ) * dim, b=(5, ) * dim, n=(nel_1d, ) * dim, ) logger.info(f"Number of {dim}d elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) # Init soln with Vortex and expected RHS = 0 vortex = Vortex2D(center=[0, 0], velocity=[0, 0]) vortex_soln = vortex(0, nodes) boundaries = {BTAG_ALL: PrescribedBoundary(vortex)} inviscid_rhs = inviscid_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, q=vortex_soln, t=0.0) err_max = discr.norm(inviscid_rhs, np.inf) eoc_rec.add_data_point(1.0 / nel_1d, err_max) message = (f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}") logger.info(message) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11)
def test_opposite_face_interpolation(ctx_getter, group_factory, mesh_name, dim, mesh_pars): logging.basicConfig(level=logging.INFO) cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, make_opposite_face_connection, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 5 def f(x): return 0.1*cl.clmath.sin(30*x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h] ) print("END GEN") elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1/mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(cl_ctx, mesh, group_factory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) bdry_connection = make_face_restriction( vol_discr, group_factory(order), FRESTR_INTERIOR_FACES) bdry_discr = bdry_connection.to_discr opp_face = make_opposite_face_connection(bdry_connection) check_connection(opp_face) bdry_x = bdry_discr.nodes()[0].with_queue(queue) bdry_f = f(bdry_x) bdry_f_2 = opp_face(queue, bdry_f) err = la.norm((bdry_f-bdry_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert ( eoc_rec.order_estimate() >= order-0.5 or eoc_rec.max_error() < 1e-13)
def test_multilump_rhs(actx_factory, dim, order, v0, use_overintegration): """Test the Euler rhs using the non-trivial 1, 2, and 3D mass lump case. The case is tested against the analytic expressions of the RHS. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() nspecies = 10 tolerance = 1e-8 maxxerr = 0.0 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for nel_1d in [4, 8, 12]: from meshmode.mesh.generation import ( generate_regular_rect_mesh, ) mesh = generate_regular_rect_mesh( a=(-1,) * dim, b=(1,) * dim, nelements_per_axis=(nel_1d,) * dim, ) logger.info(f"Number of elements: {mesh.nelements}") from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None nodes = thaw(discr.nodes(), actx) centers = make_obj_array([np.zeros(shape=(dim,)) for i in range(nspecies)]) spec_y0s = np.ones(shape=(nspecies,)) spec_amplitudes = np.ones(shape=(nspecies,)) velocity = np.zeros(shape=(dim,)) velocity[0] = v0 rho0 = 2.0 lump = MulticomponentLump(dim=dim, nspecies=nspecies, rho0=rho0, spec_centers=centers, velocity=velocity, spec_y0s=spec_y0s, spec_amplitudes=spec_amplitudes) lump_soln = lump(nodes) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(lump_soln, gas_model) def _my_boundary(discr, btag, gas_model, state_minus, **kwargs): actx = state_minus.array_context bnd_discr = discr.discr_from_dd(btag) nodes = thaw(bnd_discr.nodes(), actx) return make_fluid_state(lump(x_vec=nodes, **kwargs), gas_model) boundaries = { BTAG_ALL: PrescribedFluidBoundary(boundary_state_func=_my_boundary) } inviscid_rhs = euler_operator( discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0, quadrature_tag=quadrature_tag ) expected_rhs = lump.exact_rhs(discr, cv=lump_soln, time=0) print(f"inviscid_rhs = {inviscid_rhs}") print(f"expected_rhs = {expected_rhs}") err_max = actx.to_numpy( discr.norm((inviscid_rhs-expected_rhs), np.inf)) if err_max > maxxerr: maxxerr = err_max eoc_rec.add_data_point(1.0 / nel_1d, err_max) logger.info(f"Max error: {maxxerr}") logger.info( f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}" ) assert ( eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < tolerance )
def test_boundary_interpolation(ctx_getter, group_factory, boundary_tag, mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import (make_face_restriction, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 def f(x): return 0.1 * cl.clmath.sin(30 * x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh(FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h ]) print("END GEN") elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(cl_ctx, mesh, group_factory(order)) print("h=%s -> %d elements" % (h, sum(mgrp.nelements for mgrp in mesh.groups))) x = vol_discr.nodes()[0].with_queue(queue) vol_f = f(x) bdry_connection = make_face_restriction( vol_discr, group_factory(order), boundary_tag, per_face_groups=per_face_groups) check_connection(bdry_connection) bdry_discr = bdry_connection.to_discr bdry_x = bdry_discr.nodes()[0].with_queue(queue) bdry_f = f(bdry_x) bdry_f_2 = bdry_connection(queue, vol_f) if mesh_name == "blob" and dim == 2: mat = bdry_connection.full_resample_matrix(queue).get(queue) bdry_f_2_by_mat = mat.dot(vol_f.get()) mat_error = la.norm(bdry_f_2.get(queue=queue) - bdry_f_2_by_mat) assert mat_error < 1e-14, mat_error err = la.norm((bdry_f - bdry_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-14)
def test_all_faces_interpolation(ctx_getter, mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, make_face_to_all_faces_embedding, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 def f(x): return 0.1 * cl.clmath.sin(30 * x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh(FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h ]) print("END GEN") elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order)) print("h=%s -> %d elements" % (h, sum(mgrp.nelements for mgrp in mesh.groups))) all_face_bdry_connection = make_face_restriction( vol_discr, PolynomialWarpAndBlendGroupFactory(order), FRESTR_ALL_FACES, per_face_groups=per_face_groups) all_face_bdry_discr = all_face_bdry_connection.to_discr for ito_grp, ceg in enumerate(all_face_bdry_connection.groups): for ibatch, batch in enumerate(ceg.batches): assert np.array_equal(batch.from_element_indices.get(queue), np.arange(vol_discr.mesh.nelements)) if per_face_groups: assert ito_grp == batch.to_element_face else: assert ibatch == batch.to_element_face all_face_x = all_face_bdry_discr.nodes()[0].with_queue(queue) all_face_f = f(all_face_x) all_face_f_2 = all_face_bdry_discr.zeros(queue) for boundary_tag in [ BTAG_ALL, FRESTR_INTERIOR_FACES, ]: bdry_connection = make_face_restriction( vol_discr, PolynomialWarpAndBlendGroupFactory(order), boundary_tag, per_face_groups=per_face_groups) bdry_discr = bdry_connection.to_discr bdry_x = bdry_discr.nodes()[0].with_queue(queue) bdry_f = f(bdry_x) all_face_embedding = make_face_to_all_faces_embedding( bdry_connection, all_face_bdry_discr) check_connection(all_face_embedding) all_face_f_2 += all_face_embedding(queue, bdry_f) err = la.norm((all_face_f - all_face_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-14)
def test_uniform_rhs(actx_factory, dim, order): """Tests the inviscid rhs using a trivial constant/uniform state which should yield rhs = 0 to FP. The test is performed for 1, 2, and 3 dimensions. """ actx = actx_factory() tolerance = 1e-9 maxxerr = 0.0 from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() # for nel_1d in [4, 8, 12]: for nel_1d in [4, 8]: from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim) logger.info(f"Number of {dim}d elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) mass_input = discr.zeros(actx) + 1 energy_input = discr.zeros(actx) + 2.5 mom_input = make_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) fields = join_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input) expected_rhs = make_obj_array( [discr.zeros(actx) for i in range(len(fields))]) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = inviscid_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, q=fields, t=0.0) rhs_resid = inviscid_rhs - expected_rhs resid_split = split_conserved(dim, rhs_resid) rho_resid = resid_split.mass rhoe_resid = resid_split.energy mom_resid = resid_split.momentum rhs_split = split_conserved(dim, inviscid_rhs) rho_rhs = rhs_split.mass rhoe_rhs = rhs_split.energy rhov_rhs = rhs_split.momentum message = (f"rho_rhs = {rho_rhs}\n" f"rhoe_rhs = {rhoe_rhs}\n" f"rhov_rhs = {rhov_rhs}") logger.info(message) assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance err_max = discr.norm(rhs_resid[i], np.inf) eoc_rec0.add_data_point(1.0 / nel_1d, err_max) assert (err_max < tolerance) if err_max > maxxerr: maxxerr = err_max # set a non-zero, but uniform velocity component for i in range(len(mom_input)): mom_input[i] = discr.zeros(actx) + (-1.0)**i boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = inviscid_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, q=fields, t=0.0) rhs_resid = inviscid_rhs - expected_rhs resid_split = split_conserved(dim, rhs_resid) rho_resid = resid_split.mass rhoe_resid = resid_split.energy mom_resid = resid_split.momentum assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance err_max = discr.norm(rhs_resid[i], np.inf) eoc_rec1.add_data_point(1.0 / nel_1d, err_max) assert (err_max < tolerance) if err_max > maxxerr: maxxerr = err_max message = (f"V == 0 Errors:\n{eoc_rec0}" f"V != 0 Errors:\n{eoc_rec1}") print(message) assert (eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9) assert (eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9)
def test_boundary_interpolation(actx_factory, group_factory, boundary_tag, mesh_name, dim, mesh_pars, per_face_groups): if (group_factory is LegendreGaussLobattoTensorProductGroupFactory and mesh_name == "blob"): pytest.skip("tensor products not implemented on blobs") actx = actx_factory() if group_factory is LegendreGaussLobattoTensorProductGroupFactory: group_cls = TensorProductElementGroup else: group_cls = SimplexElementGroup from meshmode.discretization import Discretization from meshmode.discretization.connection import (make_face_restriction, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 def f(x): return 0.1 * actx.np.sin(30 * x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = float(mesh_par) #from meshmode.mesh.io import generate_gmsh, FileSource # print("BEGIN GEN") # mesh = generate_gmsh( # FileSource("blob-2d.step"), 2, order=order, # force_ambient_dim=2, # other_options=[ # "-string", "Mesh.CharacteristicLengthMax = %s;" % h] # ) # print("END GEN") from meshmode.mesh.io import read_gmsh mesh = read_gmsh("blob2d-order%d-h%s.msh" % (order, mesh_par), force_ambient_dim=2) elif mesh_name == "warp": mesh = mgen.generate_warped_rect_mesh(dim, order=order, nelements_side=mesh_par, group_cls=group_cls) h = 1 / mesh_par elif mesh_name == "rect": mesh = mgen.generate_regular_rect_mesh( a=(0, ) * dim, b=(1, ) * dim, order=order, nelements_per_axis=(mesh_par, ) * dim, group_cls=group_cls) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(actx, mesh, group_factory(order)) print("h=%s -> %d elements" % (h, sum(mgrp.nelements for mgrp in mesh.groups))) x = thaw(vol_discr.nodes()[0], actx) vol_f = f(x) bdry_connection = make_face_restriction( actx, vol_discr, group_factory(order), boundary_tag, per_face_groups=per_face_groups) check_connection(actx, bdry_connection) bdry_discr = bdry_connection.to_discr bdry_x = thaw(bdry_discr.nodes()[0], actx) bdry_f = f(bdry_x) bdry_f_2 = bdry_connection(vol_f) if mesh_name == "blob" and dim == 2 and mesh.nelements < 500: from meshmode.discretization.connection.direct import \ make_direct_full_resample_matrix mat = actx.to_numpy( make_direct_full_resample_matrix(actx, bdry_connection)) bdry_f_2_by_mat = mat.dot(flatten_to_numpy(actx, vol_f)) mat_error = la.norm( flatten_to_numpy(actx, bdry_f_2) - bdry_f_2_by_mat) assert mat_error < 1e-14, mat_error err = flat_norm(bdry_f - bdry_f_2, np.inf) eoc_rec.add_data_point(h, err) order_slack = 0.75 if mesh_name == "blob" else 0.5 print(eoc_rec) assert (eoc_rec.order_estimate() >= order - order_slack or eoc_rec.max_error() < 3.6e-13)
def test_all_faces_interpolation(actx_factory, group_factory, mesh_name, dim, mesh_pars, per_face_groups): if (group_factory is LegendreGaussLobattoTensorProductGroupFactory and mesh_name == "blob"): pytest.skip("tensor products not implemented on blobs") actx = actx_factory() if group_factory is LegendreGaussLobattoTensorProductGroupFactory: group_cls = TensorProductElementGroup else: group_cls = SimplexElementGroup from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, make_face_to_all_faces_embedding, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 def f(x): return 0.1 * actx.np.sin(30 * x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h ], target_unit="MM", ) print("END GEN") elif mesh_name == "warp": mesh = mgen.generate_warped_rect_mesh(dim, order=4, nelements_side=mesh_par, group_cls=group_cls) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(actx, mesh, group_factory(order)) print("h=%s -> %d elements" % (h, sum(mgrp.nelements for mgrp in mesh.groups))) all_face_bdry_connection = make_face_restriction( actx, vol_discr, group_factory(order), FACE_RESTR_ALL, per_face_groups=per_face_groups) all_face_bdry_discr = all_face_bdry_connection.to_discr for ito_grp, ceg in enumerate(all_face_bdry_connection.groups): for ibatch, batch in enumerate(ceg.batches): assert np.array_equal( actx.to_numpy(actx.thaw(batch.from_element_indices)), np.arange(vol_discr.mesh.nelements)) if per_face_groups: assert ito_grp == batch.to_element_face else: assert ibatch == batch.to_element_face all_face_x = thaw(all_face_bdry_discr.nodes()[0], actx) all_face_f = f(all_face_x) all_face_f_2 = all_face_bdry_discr.zeros(actx) for boundary_tag in [ BTAG_ALL, FACE_RESTR_INTERIOR, ]: bdry_connection = make_face_restriction( actx, vol_discr, group_factory(order), boundary_tag, per_face_groups=per_face_groups) bdry_discr = bdry_connection.to_discr bdry_x = thaw(bdry_discr.nodes()[0], actx) bdry_f = f(bdry_x) all_face_embedding = make_face_to_all_faces_embedding( actx, bdry_connection, all_face_bdry_discr) check_connection(actx, all_face_embedding) all_face_f_2 = all_face_f_2 + all_face_embedding(bdry_f) err = flat_norm(all_face_f - all_face_f_2, np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-14)
def test_slipwall_flux(actx_factory, dim, order): """Check for zero boundary flux. Check for vanishing flux across the slipwall. """ actx = actx_factory() wall = AdiabaticSlipBoundary() eos = IdealSingleGas() from pytools.convergence import EOCRecorder eoc = EOCRecorder() for nel_1d in [4, 8, 12]: 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) discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) nhat = thaw(actx, discr.normal(BTAG_ALL)) h = 1.0 / nel_1d from functools import partial bnd_norm = partial(discr.norm, p=np.inf, dd=BTAG_ALL) logger.info(f"Number of {dim}d elems: {mesh.nelements}") # for velocities in each direction err_max = 0.0 for vdir in range(dim): vel = np.zeros(shape=(dim, )) # for velocity directions +1, and -1 for parity in [1.0, -1.0]: vel[vdir] = parity from mirgecom.initializers import Uniform initializer = Uniform(dim=dim, velocity=vel) uniform_state = initializer(nodes) bnd_pair = wall.boundary_pair(discr, btag=BTAG_ALL, eos=eos, cv=uniform_state) # Check the total velocity component normal # to each surface. It should be zero. The # numerical fluxes cannot be zero. avg_state = 0.5 * (bnd_pair.int + bnd_pair.ext) err_max = max(err_max, bnd_norm(np.dot(avg_state.momentum, nhat))) from mirgecom.euler import _facial_flux bnd_flux = _facial_flux(discr, eos, cv_tpair=bnd_pair, local=True) err_max = max(err_max, bnd_norm(bnd_flux.mass), bnd_norm(bnd_flux.energy)) eoc.add_data_point(h, err_max) message = (f"EOC:\n{eoc}") logger.info(message) assert (eoc.order_estimate() >= order - 0.5 or eoc.max_error() < 1e-12)
def test_facial_flux(actx_factory, nspecies, order, dim): """Check the flux across element faces. The flux is checked by prescribing states (q) with known fluxes. Only uniform states are tested currently - ensuring that the Lax-Friedrichs flux terms which are proportional to jumps in state data vanish. Since the returned fluxes use state data which has been interpolated to-and-from the element faces, this test is grid-dependent. """ actx = actx_factory() tolerance = 1e-14 p0 = 1.0 from meshmode.mesh.generation import generate_regular_rect_mesh from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() for nel_1d in [4, 8, 12]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) zeros = discr.zeros(actx) ones = zeros + 1.0 mass_input = discr.zeros(actx) + 1.0 energy_input = discr.zeros(actx) + 2.5 mom_input = flat_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) mass_frac_input = flat_obj_array( [ones / ((i + 1) * 10) for i in range(nspecies)]) species_mass_input = mass_input * mass_frac_input cv = make_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) from grudge.trace_pair import interior_trace_pairs cv_interior_pairs = interior_trace_pairs(discr, cv) # Check the boundary facial fluxes as called on an interior boundary # eos = IdealSingleGas() from mirgecom.gas_model import (GasModel, make_fluid_state) gas_model = GasModel(eos=IdealSingleGas()) from mirgecom.gas_model import make_fluid_state_trace_pairs state_tpairs = make_fluid_state_trace_pairs(cv_interior_pairs, gas_model) interior_state_pair = state_tpairs[0] from mirgecom.inviscid import inviscid_facial_flux interior_face_flux = \ inviscid_facial_flux(discr, state_tpair=interior_state_pair) def inf_norm(data): if len(data) > 0: return actx.to_numpy(discr.norm(data, np.inf, dd="all_faces")) else: return 0.0 assert inf_norm(interior_face_flux.mass) < tolerance assert inf_norm(interior_face_flux.energy) < tolerance assert inf_norm(interior_face_flux.species_mass) < tolerance # The expected pressure is 1.0 (by design). And the flux diagonal is # [rhov_x*v_x + p] (etc) since we have zero velocities it's just p. # # The off-diagonals are zero. We get a {ndim}-vector for each # dimension, the flux for the x-component of momentum (for example) is: # f_momx = < 1.0, 0 , 0> , then we return f_momx .dot. normal, which # can introduce negative values. # # (Explanation courtesy of Mike Campbell, # https://github.com/illinois-ceesd/mirgecom/pull/44#discussion_r463304292) nhat = thaw(actx, discr.normal("int_faces")) mom_flux_exact = discr.project("int_faces", "all_faces", p0 * nhat) print(f"{mom_flux_exact=}") print(f"{interior_face_flux.momentum=}") momerr = inf_norm(interior_face_flux.momentum - mom_flux_exact) assert momerr < tolerance eoc_rec0.add_data_point(1.0 / nel_1d, momerr) # Check the boundary facial fluxes as called on a domain boundary dir_mass = discr.project("vol", BTAG_ALL, mass_input) dir_e = discr.project("vol", BTAG_ALL, energy_input) dir_mom = discr.project("vol", BTAG_ALL, mom_input) dir_mf = discr.project("vol", BTAG_ALL, species_mass_input) dir_bc = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) dir_bval = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) state_tpair = TracePair(BTAG_ALL, interior=make_fluid_state(dir_bval, gas_model), exterior=make_fluid_state(dir_bc, gas_model)) boundary_flux = inviscid_facial_flux(discr, state_tpair=state_tpair) assert inf_norm(boundary_flux.mass) < tolerance assert inf_norm(boundary_flux.energy) < tolerance assert inf_norm(boundary_flux.species_mass) < tolerance nhat = thaw(actx, discr.normal(BTAG_ALL)) mom_flux_exact = discr.project(BTAG_ALL, "all_faces", p0 * nhat) momerr = inf_norm(boundary_flux.momentum - mom_flux_exact) assert momerr < tolerance eoc_rec1.add_data_point(1.0 / nel_1d, momerr) logger.info(f"standalone Errors:\n{eoc_rec0}" f"boundary Errors:\n{eoc_rec1}") assert (eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9) assert (eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9)
def test_refinement_connection(ctx_getter, group_factory, mesh_name, dim, mesh_pars, mesh_order, refine_flags, plot_mesh=False): from random import seed seed(13) # Discretization order order = 5 cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import (make_refinement_connection, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() def f(x): from six.moves import reduce return 0.1 * reduce(lambda x, y: x * cl.clmath.sin(5 * y), x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "circle": assert dim == 1 h = 1 / mesh_par mesh = make_curve_mesh(partial(ellipse, 1), np.linspace(0, 1, mesh_par + 1), order=mesh_order) elif mesh_name == "blob": if mesh_order == 5: pytest.xfail( "https://gitlab.tiker.net/inducer/meshmode/issues/2") assert dim == 2 h = mesh_par mesh = gen_blob_mesh(h, mesh_order) elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=mesh_order, n=mesh_par) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} discr = Discretization(cl_ctx, mesh, group_factory(order)) refiner = Refiner(mesh) flags = refine_flags(mesh) refiner.refine(flags) connection = make_refinement_connection(refiner, discr, group_factory(order)) check_connection(connection) fine_discr = connection.to_discr x = discr.nodes().with_queue(queue) x_fine = fine_discr.nodes().with_queue(queue) f_coarse = f(x) f_interp = connection(queue, f_coarse).with_queue(queue) f_true = f(x_fine).with_queue(queue) if plot_mesh: import matplotlib.pyplot as plt x = x.get(queue) err = np.array(np.log10(1e-16 + np.abs((f_interp - f_true).get(queue))), dtype=float) import matplotlib.cm as cm cmap = cm.ScalarMappable(cmap=cm.jet) cmap.set_array(err) plt.scatter(x[0], x[1], c=cmap.to_rgba(err), s=20, cmap=cmap) plt.colorbar(cmap) plt.show() import numpy.linalg as la err = la.norm((f_interp - f_true).get(queue), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-14)
def test_uniform_rhs(actx_factory, nspecies, dim, order): """Test the inviscid rhs using a trivial constant/uniform state. This state should yield rhs = 0 to FP. The test is performed for 1, 2, and 3 dimensions, with orders 1, 2, and 3, with and without passive species. """ actx = actx_factory() tolerance = 1e-9 from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() # for nel_1d in [4, 8, 12]: for nel_1d in [4, 8]: 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(f"Number of {dim}d elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) zeros = discr.zeros(actx) ones = zeros + 1.0 mass_input = discr.zeros(actx) + 1 energy_input = discr.zeros(actx) + 2.5 mom_input = make_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) mass_frac_input = flat_obj_array( [ones / ((i + 1) * 10) for i in range(nspecies)]) species_mass_input = mass_input * mass_frac_input num_equations = dim + 2 + len(species_mass_input) cv = make_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) expected_rhs = make_conserved( dim, q=make_obj_array([discr.zeros(actx) for i in range(num_equations)])) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, cv=cv, time=0.0) rhs_resid = inviscid_rhs - expected_rhs rho_resid = rhs_resid.mass rhoe_resid = rhs_resid.energy mom_resid = rhs_resid.momentum rhoy_resid = rhs_resid.species_mass rho_rhs = inviscid_rhs.mass rhoe_rhs = inviscid_rhs.energy rhov_rhs = inviscid_rhs.momentum rhoy_rhs = inviscid_rhs.species_mass logger.info(f"rho_rhs = {rho_rhs}\n" f"rhoe_rhs = {rhoe_rhs}\n" f"rhov_rhs = {rhov_rhs}\n" f"rhoy_rhs = {rhoy_rhs}\n") assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance for i in range(nspecies): assert discr.norm(rhoy_resid[i], np.inf) < tolerance err_max = discr.norm(rho_resid, np.inf) eoc_rec0.add_data_point(1.0 / nel_1d, err_max) # set a non-zero, but uniform velocity component for i in range(len(mom_input)): mom_input[i] = discr.zeros(actx) + (-1.0)**i cv = make_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, cv=cv, time=0.0) rhs_resid = inviscid_rhs - expected_rhs rho_resid = rhs_resid.mass rhoe_resid = rhs_resid.energy mom_resid = rhs_resid.momentum rhoy_resid = rhs_resid.species_mass assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance for i in range(nspecies): assert discr.norm(rhoy_resid[i], np.inf) < tolerance err_max = discr.norm(rho_resid, np.inf) eoc_rec1.add_data_point(1.0 / nel_1d, err_max) logger.info(f"V == 0 Errors:\n{eoc_rec0}" f"V != 0 Errors:\n{eoc_rec1}") assert (eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9) assert (eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9)
def test_multilump_rhs(actx_factory, dim, order, v0): """Test the Euler rhs using the non-trivial 1, 2, and 3D mass lump case. The case is tested against the analytic expressions of the RHS. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() nspecies = 10 tolerance = 1e-8 maxxerr = 0.0 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for nel_1d in [4, 8, 16]: from meshmode.mesh.generation import ( generate_regular_rect_mesh, ) mesh = generate_regular_rect_mesh( a=(-1, ) * dim, b=(1, ) * dim, nelements_per_axis=(nel_1d, ) * dim, ) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) centers = make_obj_array( [np.zeros(shape=(dim, )) for i in range(nspecies)]) spec_y0s = np.ones(shape=(nspecies, )) spec_amplitudes = np.ones(shape=(nspecies, )) velocity = np.zeros(shape=(dim, )) velocity[0] = v0 rho0 = 2.0 lump = MulticomponentLump(dim=dim, nspecies=nspecies, rho0=rho0, spec_centers=centers, velocity=velocity, spec_y0s=spec_y0s, spec_amplitudes=spec_amplitudes) lump_soln = lump(nodes) boundaries = { BTAG_ALL: PrescribedInviscidBoundary(fluid_solution_func=lump) } inviscid_rhs = euler_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, cv=lump_soln, time=0.0) expected_rhs = lump.exact_rhs(discr, cv=lump_soln, time=0) print(f"inviscid_rhs = {inviscid_rhs}") print(f"expected_rhs = {expected_rhs}") err_max = discr.norm((inviscid_rhs - expected_rhs).join(), np.inf) if err_max > maxxerr: maxxerr = err_max eoc_rec.add_data_point(1.0 / nel_1d, err_max) logger.info(f"Max error: {maxxerr}") logger.info(f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}") assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < tolerance)
def test_vortex_rhs(actx_factory, order, use_overintegration): """Test the inviscid rhs using the non-trivial 2D isentropic vortex. The case is configured to yield rhs = 0. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() dim = 2 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() from meshmode.mesh.generation import generate_regular_rect_mesh for nel_1d in [32, 48, 64]: mesh = generate_regular_rect_mesh( a=(-5,) * dim, b=(5,) * dim, nelements_per_axis=(nel_1d,) * dim, ) logger.info( f"Number of {dim}d elements: {mesh.nelements}" ) from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None nodes = thaw(discr.nodes(), actx) # Init soln with Vortex and expected RHS = 0 vortex = Vortex2D(center=[0, 0], velocity=[0, 0]) vortex_soln = vortex(nodes) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(vortex_soln, gas_model) def _vortex_boundary(discr, btag, gas_model, state_minus, **kwargs): actx = state_minus.array_context bnd_discr = discr.discr_from_dd(btag) nodes = thaw(bnd_discr.nodes(), actx) return make_fluid_state(vortex(x_vec=nodes, **kwargs), gas_model) boundaries = { BTAG_ALL: PrescribedFluidBoundary(boundary_state_func=_vortex_boundary) } inviscid_rhs = euler_operator( discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0, quadrature_tag=quadrature_tag) err_max = max_component_norm(discr, inviscid_rhs, np.inf) eoc_rec.add_data_point(1.0 / nel_1d, err_max) logger.info( f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}" ) assert ( eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11 )
def test_boundary_interpolation(ctx_getter, group_factory, boundary_tag, mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 def f(x): return 0.1*cl.clmath.sin(30*x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h] ) print("END GEN") elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1/mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(cl_ctx, mesh, group_factory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) x = vol_discr.nodes()[0].with_queue(queue) vol_f = f(x) bdry_connection = make_face_restriction( vol_discr, group_factory(order), boundary_tag, per_face_groups=per_face_groups) check_connection(bdry_connection) bdry_discr = bdry_connection.to_discr bdry_x = bdry_discr.nodes()[0].with_queue(queue) bdry_f = f(bdry_x) bdry_f_2 = bdry_connection(queue, vol_f) if mesh_name == "blob" and dim == 2: mat = bdry_connection.full_resample_matrix(queue).get(queue) bdry_f_2_by_mat = mat.dot(vol_f.get()) mat_error = la.norm(bdry_f_2.get(queue=queue) - bdry_f_2_by_mat) assert mat_error < 1e-14, mat_error err = la.norm((bdry_f-bdry_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert ( eoc_rec.order_estimate() >= order-0.5 or eoc_rec.max_error() < 1e-14)
def test_refinement_connection(actx_factory, refiner_cls, group_factory, mesh_name, dim, mesh_pars, mesh_order, refine_flags, visualize=False): group_cls = group_factory.mesh_group_class if issubclass(group_cls, TensorProductElementGroup): if mesh_name in ["circle", "blob"]: pytest.skip("mesh does not have tensor product support") from random import seed seed(13) actx = actx_factory() # discretization order order = 5 from meshmode.discretization import Discretization from meshmode.discretization.connection import (make_refinement_connection, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "circle": assert dim == 1 h = 1 / mesh_par mesh = make_curve_mesh(partial(ellipse, 1), np.linspace(0, 1, mesh_par + 1), order=mesh_order) elif mesh_name == "blob": if mesh_order == 5: pytest.xfail( "https://gitlab.tiker.net/inducer/meshmode/issues/2") assert dim == 2 mesh = get_blob_mesh(mesh_par, mesh_order) h = float(mesh_par) elif mesh_name == "warp": mesh = mgen.generate_warped_rect_mesh(dim, order=mesh_order, n=mesh_par, group_cls=group_cls) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} from meshmode.mesh.processing import find_bounding_box mesh_bbox_low, mesh_bbox_high = find_bounding_box(mesh) mesh_ext = mesh_bbox_high - mesh_bbox_low def f(x): result = 1 if mesh_name == "blob": factor = 15 else: factor = 9 for iaxis in range(len(x)): result = result * actx.np.sin(factor * (x[iaxis] / mesh_ext[iaxis])) return result discr = Discretization(actx, mesh, group_factory(order)) refiner = refiner_cls(mesh) flags = refine_flags(mesh) refiner.refine(flags) connection = make_refinement_connection(actx, refiner, discr, group_factory(order)) check_connection(actx, connection) fine_discr = connection.to_discr x = thaw(actx, discr.nodes()) x_fine = thaw(actx, fine_discr.nodes()) f_coarse = f(x) f_interp = connection(f_coarse) f_true = f(x_fine) if visualize == "dots": import matplotlib.pyplot as plt x = x.get(actx.queue) err = np.array( np.log10(1e-16 + np.abs((f_interp - f_true).get(actx.queue))), dtype=float) import matplotlib.cm as cm cmap = cm.ScalarMappable(cmap=cm.jet) cmap.set_array(err) plt.scatter(x[0], x[1], c=cmap.to_rgba(err), s=20, cmap=cmap) plt.colorbar(cmap) plt.show() elif visualize == "vtk": from meshmode.discretization.visualization import make_visualizer fine_vis = make_visualizer(actx, fine_discr, mesh_order) fine_vis.write_vtk_file( "refine-fine-%s-%dd-%s.vtu" % (mesh_name, dim, mesh_par), [ ("f_interp", f_interp), ("f_true", f_true), ]) err = actx.np.linalg.norm(f_interp - f_true, np.inf) eoc_rec.add_data_point(h, err) order_slack = 0.5 if mesh_name == "blob" and order > 1: order_slack = 1 print(eoc_rec) assert (eoc_rec.order_estimate() >= order - order_slack or eoc_rec.max_error() < 1e-14)
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
def test_opposite_face_interpolation(actx_factory, group_factory, mesh_name, dim, mesh_pars): if (group_factory is LegendreGaussLobattoTensorProductGroupFactory and mesh_name in ["segment", "blob"]): pytest.skip("tensor products not implemented on blobs") logging.basicConfig(level=logging.INFO) actx = actx_factory() if group_factory is LegendreGaussLobattoTensorProductGroupFactory: group_cls = TensorProductElementGroup else: group_cls = SimplexElementGroup from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, make_opposite_face_connection, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 5 def f(x): return 0.1 * actx.np.sin(30 * x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "segment": assert dim == 1 mesh = mgen.generate_box_mesh([np.linspace(-0.5, 0.5, mesh_par)], order=order, group_cls=group_cls) h = 1.0 / mesh_par elif mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h ], target_unit="MM", ) print("END GEN") elif mesh_name == "warp": mesh = mgen.generate_warped_rect_mesh(dim, order=order, nelements_side=mesh_par, group_cls=group_cls) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(actx, mesh, group_factory(order)) print("h=%s -> %d elements" % (h, sum(mgrp.nelements for mgrp in mesh.groups))) bdry_connection = make_face_restriction(actx, vol_discr, group_factory(order), FACE_RESTR_INTERIOR) bdry_discr = bdry_connection.to_discr opp_face = make_opposite_face_connection(actx, bdry_connection) check_connection(actx, opp_face) bdry_x = thaw(bdry_discr.nodes()[0], actx) bdry_f = f(bdry_x) bdry_f_2 = opp_face(bdry_f) err = flat_norm(bdry_f - bdry_f_2, np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1.7e-13)
def test_refinement_connection( ctx_getter, refiner_cls, group_factory, mesh_name, dim, mesh_pars, mesh_order, refine_flags, visualize=False): from random import seed seed(13) # Discretization order order = 5 cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_refinement_connection, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "circle": assert dim == 1 h = 1 / mesh_par mesh = make_curve_mesh( partial(ellipse, 1), np.linspace(0, 1, mesh_par + 1), order=mesh_order) elif mesh_name == "blob": if mesh_order == 5: pytest.xfail("https://gitlab.tiker.net/inducer/meshmode/issues/2") assert dim == 2 mesh = get_blob_mesh(mesh_par, mesh_order) h = float(mesh_par) elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=mesh_order, n=mesh_par) h = 1/mesh_par else: raise ValueError("mesh_name not recognized") # }}} from meshmode.mesh.processing import find_bounding_box mesh_bbox_low, mesh_bbox_high = find_bounding_box(mesh) mesh_ext = mesh_bbox_high-mesh_bbox_low def f(x): result = 1 if mesh_name == "blob": factor = 15 else: factor = 9 for iaxis in range(len(x)): result = result * cl.clmath.sin(factor * (x[iaxis]/mesh_ext[iaxis])) return result discr = Discretization(cl_ctx, mesh, group_factory(order)) refiner = refiner_cls(mesh) flags = refine_flags(mesh) refiner.refine(flags) connection = make_refinement_connection( refiner, discr, group_factory(order)) check_connection(connection) fine_discr = connection.to_discr x = discr.nodes().with_queue(queue) x_fine = fine_discr.nodes().with_queue(queue) f_coarse = f(x) f_interp = connection(queue, f_coarse).with_queue(queue) f_true = f(x_fine).with_queue(queue) if visualize == "dots": import matplotlib.pyplot as plt x = x.get(queue) err = np.array(np.log10( 1e-16 + np.abs((f_interp - f_true).get(queue))), dtype=float) import matplotlib.cm as cm cmap = cm.ScalarMappable(cmap=cm.jet) cmap.set_array(err) plt.scatter(x[0], x[1], c=cmap.to_rgba(err), s=20, cmap=cmap) plt.colorbar(cmap) plt.show() elif visualize == "vtk": from meshmode.discretization.visualization import make_visualizer fine_vis = make_visualizer(queue, fine_discr, mesh_order) fine_vis.write_vtk_file( "refine-fine-%s-%dd-%s.vtu" % (mesh_name, dim, mesh_par), [ ("f_interp", f_interp), ("f_true", f_true), ]) import numpy.linalg as la err = la.norm((f_interp - f_true).get(queue), np.inf) eoc_rec.add_data_point(h, err) order_slack = 0.5 if mesh_name == "blob" and order > 1: order_slack = 1 print(eoc_rec) assert ( eoc_rec.order_estimate() >= order-order_slack or eoc_rec.max_error() < 1e-14)
def test_facial_flux(actx_factory, order, dim): """Check the flux across element faces by prescribing states (q) with known fluxes. Only uniform states are tested currently - ensuring that the Lax-Friedrichs flux terms which are proportional to jumps in state data vanish. Since the returned fluxes use state data which has been interpolated to-and-from the element faces, this test is grid-dependent. """ actx = actx_factory() tolerance = 1e-14 p0 = 1.0 from meshmode.mesh.generation import generate_regular_rect_mesh from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() for nel_1d in [4, 8, 12]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) mass_input = discr.zeros(actx) + 1.0 energy_input = discr.zeros(actx) + 2.5 mom_input = flat_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) fields = join_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input) from mirgecom.euler import _facial_flux interior_face_flux = _facial_flux(discr, eos=IdealSingleGas(), q_tpair=interior_trace_pair( discr, fields)) from functools import partial fnorm = partial(discr.norm, p=np.inf, dd="all_faces") iff_split = split_conserved(dim, interior_face_flux) assert fnorm(iff_split.mass) < tolerance assert fnorm(iff_split.energy) < tolerance # The expected pressure 1.0 (by design). And the flux diagonal is # [rhov_x*v_x + p] (etc) since we have zero velocities it's just p. # # The off-diagonals are zero. We get a {ndim}-vector for each # dimension, the flux for the x-component of momentum (for example) is: # f_momx = < 1.0, 0 , 0> , then we return f_momx .dot. normal, which # can introduce negative values. # # (Explanation courtesy of Mike Campbell, # https://github.com/illinois-ceesd/mirgecom/pull/44#discussion_r463304292) momerr = fnorm(iff_split.momentum) - p0 assert momerr < tolerance eoc_rec0.add_data_point(1.0 / nel_1d, momerr) # Check the boundary facial fluxes as called on a boundary dir_mass = discr.project("vol", BTAG_ALL, mass_input) dir_e = discr.project("vol", BTAG_ALL, energy_input) dir_mom = discr.project("vol", BTAG_ALL, mom_input) dir_bval = join_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom) dir_bc = join_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom) boundary_flux = _facial_flux(discr, eos=IdealSingleGas(), q_tpair=TracePair(BTAG_ALL, interior=dir_bval, exterior=dir_bc)) bf_split = split_conserved(dim, boundary_flux) assert fnorm(bf_split.mass) < tolerance assert fnorm(bf_split.energy) < tolerance momerr = fnorm(bf_split.momentum) - p0 assert momerr < tolerance eoc_rec1.add_data_point(1.0 / nel_1d, momerr) message = (f"standalone Errors:\n{eoc_rec0}" f"boundary Errors:\n{eoc_rec1}") logger.info(message) assert (eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9) assert (eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9)
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) 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 surface area dd = dof_desc.DD_VOLUME ones_volm = volume_discr.zeros(actx) + 1 approx_surface_area = actx.to_numpy(op.integral(dcoll, dd, ones_volm)) logger.info("surface: got {:.5e} / expected {:.5e}".format( approx_surface_area, surface_area)) area_error = abs(approx_surface_area - surface_area) / abs(surface_area) # }}} # 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, area_error) # }}} logger.info("surface area error\n%s", str(eoc)) assert eoc.max_error() < 3e-13 or eoc.order_estimate() > builder.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) # }}} # {{{ 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 test_diffusion_accuracy(actx_factory, problem, nsteps, dt, scales, order, visualize=False): """ Checks the accuracy of the diffusion operator by solving the heat equation for a given problem setup. """ actx = actx_factory() p = problem sym_diffusion_u = sym_diffusion(p.dim, p.sym_alpha, p.sym_u) # In order to support manufactured solutions, we modify the heat equation # to add a source term f. If the solution is exact, this term should be 0. sym_t = pmbl.var("t") sym_f = sym.diff(sym_t)(p.sym_u) - sym_diffusion_u from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for n in scales: mesh = p.get_mesh(n) from grudge.eager import EagerDGDiscretization from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory, \ PolynomialWarpAndBlendGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: PolynomialWarpAndBlendGroupFactory(order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(3 * order), }) nodes = thaw(actx, discr.nodes()) def sym_eval(expr, t): return sym.EvaluationMapper({"x": nodes, "t": t})(expr) alpha = sym_eval(p.sym_alpha, 0.) if isinstance(alpha, DOFArray): discr_tag = DISCR_TAG_QUAD else: discr_tag = DISCR_TAG_BASE def get_rhs(t, u): return ( diffusion_operator(discr, quad_tag=discr_tag, alpha=alpha, boundaries=p.get_boundaries(discr, actx, t), u=u) + sym_eval(sym_f, t)) t = 0. u = sym_eval(p.sym_u, t) from mirgecom.integrators import rk4_step for _ in range(nsteps): u = rk4_step(u, t, dt, get_rhs) t += dt expected_u = sym_eval(p.sym_u, t) rel_linf_err = (discr.norm(u - expected_u, np.inf) / discr.norm(expected_u, np.inf)) eoc_rec.add_data_point(1. / n, rel_linf_err) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(discr, discr.order + 3) vis.write_vtk_file( "diffusion_accuracy_{order}_{n}.vtu".format(order=order, n=n), [ ("u", u), ("expected_u", expected_u), ]) print("L^inf error:") print(eoc_rec) # Expected convergence rates from Hesthaven/Warburton book expected_order = order + 1 if order % 2 == 0 else order assert (eoc_rec.order_estimate() >= expected_order - 0.5 or eoc_rec.max_error() < 1e-11)
def test_opposite_face_interpolation(ctx_getter, group_factory, mesh_name, dim, mesh_pars): logging.basicConfig(level=logging.INFO) cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, make_opposite_face_connection, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 5 def f(x): return 0.1 * cl.clmath.sin(30 * x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh(FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h ]) print("END GEN") elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(cl_ctx, mesh, group_factory(order)) print("h=%s -> %d elements" % (h, sum(mgrp.nelements for mgrp in mesh.groups))) bdry_connection = make_face_restriction(vol_discr, group_factory(order), FRESTR_INTERIOR_FACES) bdry_discr = bdry_connection.to_discr opp_face = make_opposite_face_connection(bdry_connection) check_connection(opp_face) bdry_x = bdry_discr.nodes()[0].with_queue(queue) bdry_f = f(bdry_x) bdry_f_2 = opp_face(queue, bdry_f) err = la.norm((bdry_f - bdry_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-13)
def test_all_faces_interpolation(ctx_getter, mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( make_face_restriction, make_face_to_all_faces_embedding, check_connection) from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() order = 4 def f(x): return 0.1*cl.clmath.sin(30*x) for mesh_par in mesh_pars: # {{{ get mesh if mesh_name == "blob": assert dim == 2 h = mesh_par from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = %s;" % h] ) print("END GEN") elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1/mesh_par else: raise ValueError("mesh_name not recognized") # }}} vol_discr = Discretization(cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) all_face_bdry_connection = make_face_restriction( vol_discr, PolynomialWarpAndBlendGroupFactory(order), FRESTR_ALL_FACES, per_face_groups=per_face_groups) all_face_bdry_discr = all_face_bdry_connection.to_discr for ito_grp, ceg in enumerate(all_face_bdry_connection.groups): for ibatch, batch in enumerate(ceg.batches): assert np.array_equal( batch.from_element_indices.get(queue), np.arange(vol_discr.mesh.nelements)) if per_face_groups: assert ito_grp == batch.to_element_face else: assert ibatch == batch.to_element_face all_face_x = all_face_bdry_discr.nodes()[0].with_queue(queue) all_face_f = f(all_face_x) all_face_f_2 = all_face_bdry_discr.zeros(queue) for boundary_tag in [ BTAG_ALL, FRESTR_INTERIOR_FACES, ]: bdry_connection = make_face_restriction( vol_discr, PolynomialWarpAndBlendGroupFactory(order), boundary_tag, per_face_groups=per_face_groups) bdry_discr = bdry_connection.to_discr bdry_x = bdry_discr.nodes()[0].with_queue(queue) bdry_f = f(bdry_x) all_face_embedding = make_face_to_all_faces_embedding( bdry_connection, all_face_bdry_discr) check_connection(all_face_embedding) all_face_f_2 += all_face_embedding(queue, bdry_f) err = la.norm((all_face_f-all_face_f_2).get(), np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) assert ( eoc_rec.order_estimate() >= order-0.5 or eoc_rec.max_error() < 1e-14)
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_uniform_rhs(actx_factory, nspecies, dim, order, use_overintegration): """Test the inviscid rhs using a trivial constant/uniform state. This state should yield rhs = 0 to FP. The test is performed for 1, 2, and 3 dimensions, with orders 1, 2, and 3, with and without passive species. """ actx = actx_factory() tolerance = 1e-9 from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() # for nel_1d in [4, 8, 12]: for nel_1d in [4, 8]: 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( f"Number of {dim}d elements: {mesh.nelements}" ) from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None zeros = discr.zeros(actx) ones = zeros + 1.0 mass_input = discr.zeros(actx) + 1 energy_input = discr.zeros(actx) + 2.5 mom_input = make_obj_array( [discr.zeros(actx) for i in range(discr.dim)] ) mass_frac_input = flat_obj_array( [ones / ((i + 1) * 10) for i in range(nspecies)] ) species_mass_input = mass_input * mass_frac_input num_equations = dim + 2 + len(species_mass_input) cv = make_conserved( dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(cv, gas_model) expected_rhs = make_conserved( dim, q=make_obj_array([discr.zeros(actx) for i in range(num_equations)]) ) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0, quadrature_tag=quadrature_tag) rhs_resid = inviscid_rhs - expected_rhs rho_resid = rhs_resid.mass rhoe_resid = rhs_resid.energy mom_resid = rhs_resid.momentum rhoy_resid = rhs_resid.species_mass rho_rhs = inviscid_rhs.mass rhoe_rhs = inviscid_rhs.energy rhov_rhs = inviscid_rhs.momentum rhoy_rhs = inviscid_rhs.species_mass logger.info( f"rho_rhs = {rho_rhs}\n" f"rhoe_rhs = {rhoe_rhs}\n" f"rhov_rhs = {rhov_rhs}\n" f"rhoy_rhs = {rhoy_rhs}\n" ) def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) assert inf_norm(rho_resid) < tolerance assert inf_norm(rhoe_resid) < tolerance for i in range(dim): assert inf_norm(mom_resid[i]) < tolerance for i in range(nspecies): assert inf_norm(rhoy_resid[i]) < tolerance err_max = inf_norm(rho_resid) eoc_rec0.add_data_point(1.0 / nel_1d, err_max) # set a non-zero, but uniform velocity component for i in range(len(mom_input)): mom_input[i] = discr.zeros(actx) + (-1.0) ** i cv = make_conserved( dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(cv, gas_model) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0) rhs_resid = inviscid_rhs - expected_rhs rho_resid = rhs_resid.mass rhoe_resid = rhs_resid.energy mom_resid = rhs_resid.momentum rhoy_resid = rhs_resid.species_mass assert inf_norm(rho_resid) < tolerance assert inf_norm(rhoe_resid) < tolerance for i in range(dim): assert inf_norm(mom_resid[i]) < tolerance for i in range(nspecies): assert inf_norm(rhoy_resid[i]) < tolerance err_max = inf_norm(rho_resid) eoc_rec1.add_data_point(1.0 / nel_1d, err_max) logger.info( f"V == 0 Errors:\n{eoc_rec0}" f"V != 0 Errors:\n{eoc_rec1}" ) assert ( eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9 ) assert ( eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9 )