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 main(write_output=True): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), force_device_scalars=True, ) from meshmode.mesh import BTAG_ALL from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim=2, order=4, nelements_side=6) dcoll = DiscretizationCollection(actx, mesh, order=4) nodes = thaw(dcoll.nodes(), actx) bdry_nodes = thaw(dcoll.nodes(dd=BTAG_ALL), actx) bdry_normals = thaw(dcoll.normal(dd=BTAG_ALL), actx) if write_output: vis = shortcuts.make_visualizer(dcoll) vis.write_vtk_file("geo.vtu", [("nodes", nodes)]) bvis = shortcuts.make_boundary_visualizer(dcoll) bvis.write_vtk_file("bgeo.vtu", [("bdry normals", bdry_normals), ("bdry nodes", bdry_nodes)])
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 simple_mpi_communication_entrypoint(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue, force_device_scalars=True) from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis from meshmode.mesh import BTAG_ALL from mpi4py import MPI comm = MPI.COMM_WORLD num_parts = comm.Get_size() mesh_dist = MPIMeshDistributor(comm) if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-1,)*2, b=(1,)*2, nelements_per_axis=(2,)*2) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) else: local_mesh = mesh_dist.receive_mesh_part() dcoll = DiscretizationCollection(actx, local_mesh, order=5, mpi_communicator=comm) x = thaw(dcoll.nodes(), actx) myfunc = actx.np.sin(np.dot(x, [2, 3])) from grudge.dof_desc import as_dofdesc dd_int = as_dofdesc("int_faces") dd_vol = as_dofdesc("vol") dd_af = as_dofdesc("all_faces") all_faces_func = op.project(dcoll, dd_vol, dd_af, myfunc) int_faces_func = op.project(dcoll, dd_vol, dd_int, myfunc) bdry_faces_func = op.project(dcoll, BTAG_ALL, dd_af, op.project(dcoll, dd_vol, BTAG_ALL, myfunc)) hopefully_zero = ( op.project( dcoll, "int_faces", "all_faces", dcoll.opposite_face_connection()(int_faces_func) ) + sum(op.project(dcoll, tpair.dd, "all_faces", tpair.int) for tpair in op.cross_rank_trace_pairs(dcoll, myfunc)) ) - (all_faces_func - bdry_faces_func) error = actx.to_numpy(flat_norm(hopefully_zero, ord=np.inf)) print(__file__) with np.printoptions(threshold=100000000, suppress=True): logger.debug(hopefully_zero) logger.info("error: %.5e", error) assert error < 1e-14
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 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_nodal_reductions(actx_factory): actx = actx_factory() from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=1) 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]) def g(x): return actx.np.cos(2 * x[0]) def h(x): return -actx.np.tan(5 * x[0]) fields = make_obj_array([f(x), g(x), h(x)]) f_ref = actx.to_numpy(flatten(fields[0])) g_ref = actx.to_numpy(flatten(fields[1])) h_ref = actx.to_numpy(flatten(fields[2])) concat_fields = np.concatenate([f_ref, g_ref, h_ref]) for inner_grudge_op, np_op in [(op.nodal_sum, np.sum), (op.nodal_max, np.max), (op.nodal_min, np.min)]: # FIXME: Remove this once all grudge reductions return device scalars def grudge_op(dcoll, dd, vec): res = inner_grudge_op(dcoll, dd, vec) from numbers import Number if not isinstance(res, Number): return actx.to_numpy(res) else: return res # Componentwise reduction checks assert np.isclose(grudge_op(dcoll, "vol", fields[0]), np_op(f_ref), rtol=1e-13) assert np.isclose(grudge_op(dcoll, "vol", fields[1]), np_op(g_ref), rtol=1e-13) assert np.isclose(grudge_op(dcoll, "vol", fields[2]), np_op(h_ref), rtol=1e-13) # Test nodal reductions work on object arrays assert np.isclose(grudge_op(dcoll, "vol", fields), np_op(concat_fields), rtol=1e-13)
def parametrization_derivative(actx: ArrayContext, dcoll: DiscretizationCollection, dd) -> MultiVector: r"""Computes the product of forward metric derivatives spanning the tangent space with topological dimension *dim*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization. :returns: a :class:`pymbolic.geometric_algebra.MultiVector` containing the product of metric derivatives. """ if dd is None: dd = DD_VOLUME dim = dcoll.discr_from_dd(dd).dim if dim == 0: from pymbolic.geometric_algebra import get_euclidean_space return MultiVector(_signed_face_ones(actx, dcoll, dd), space=get_euclidean_space(dcoll.ambient_dim)) from pytools import product return product( forward_metric_derivative_mv(actx, dcoll, rst_axis, dd) for rst_axis in range(dim))
def forward_metric_derivative_mat(actx: ArrayContext, dcoll: DiscretizationCollection, dd=None) -> np.ndarray: r"""Computes the forward metric derivative matrix, also commonly called the Jacobian matrix, with entries defined as the forward metric derivatives: .. math:: J = \left\lbrack \frac{\partial x_i}{\partial \xi_j} \right\rbrack_{(0, 0) \leq (i, j) \leq (n, m)} where :math:`x_1, \dots, x_n` denote the physical coordinates and :math:`\xi_1, \dots, \xi_m` denote coordinates on the reference element. Note that, in the case of immersed manifolds, `J` is not necessarily a square matrix. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization. :returns: a matrix containing the evaluated forward metric derivatives of each physical coordinate, with respect to each reference coordinate. """ ambient_dim = dcoll.ambient_dim if dd is None: dd = DD_VOLUME dim = dcoll.discr_from_dd(dd).dim result = np.zeros((ambient_dim, dim), dtype=object) for j in range(dim): result[:, j] = forward_metric_derivative_vector(actx, dcoll, j, dd=dd) return result
def test_inverse_metric(actx_factory, dim): actx = actx_factory() mesh = mgen.generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(6, ) * dim, order=4) def m(x): result = np.empty_like(x) result[0] = (1.5 * x[0] + np.cos(x[0]) + 0.1 * np.sin(10 * x[1])) result[1] = (0.05 * np.cos(10 * x[0]) + 1.3 * x[1] + np.sin(x[1])) if len(x) == 3: result[2] = x[2] return result from meshmode.mesh.processing import map_mesh mesh = map_mesh(mesh, m) dcoll = DiscretizationCollection(actx, mesh, order=4) from grudge.geometry import \ forward_metric_derivative_mat, inverse_metric_derivative_mat mat = forward_metric_derivative_mat(actx, dcoll).dot( inverse_metric_derivative_mat(actx, dcoll)) for i in range(mesh.dim): for j in range(mesh.dim): tgt = 1 if i == j else 0 err = flat_norm(mat[i, j] - tgt, ord=np.inf) logger.info("error[%d, %d]: %.5e", i, j, err) assert err < 1.0e-12, (i, j, err)
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_external_call(actx_factory): actx = actx_factory() def double(queue, x): return 2 * x dims = 2 mesh = mgen.generate_regular_rect_mesh(a=(0, ) * dims, b=(1, ) * dims, nelements_per_axis=(4, ) * dims) discr = DiscretizationCollection(actx, mesh, order=1) ones = sym.Ones(dof_desc.DD_VOLUME) op = (ones * 3 + sym.FunctionSymbol("double")(ones)) from grudge.function_registry import (base_function_registry, register_external_function) freg = register_external_function(base_function_registry, "double", implementation=double, dd=dof_desc.DD_VOLUME) bound_op = bind(discr, op, function_registry=freg) result = bound_op(actx, double=double) assert actx.to_numpy(flatten(result) == 5).all()
def test_non_geometric_factors(actx_factory, name): from grudge.dt_utils import dt_non_geometric_factors actx = actx_factory() # {{{ cases if name == "interval": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=1) elif name == "box2d": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=2) elif name == "box3d": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=3) else: raise ValueError("unknown geometry name: %s" % name) # }}} factors = [] degrees = list(range(1, 8)) for degree in degrees: mesh = builder.get_mesh(1, degree) dcoll = DiscretizationCollection(actx, mesh, order=degree) factors.append(min(dt_non_geometric_factors(dcoll))) # Crude estimate, factors should behave like 1/N**2 factors = np.asarray(factors) lower_bounds = 1 / (np.asarray(degrees)**2) upper_bounds = 6.295 * lower_bounds assert all(lower_bounds <= factors) assert all(factors <= upper_bounds)
def main(write_output=True): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) from meshmode.mesh import BTAG_ALL from meshmode.mesh.generation import generate_warped_rect_mesh mesh = generate_warped_rect_mesh(dim=2, order=4, nelements_side=6) discr = DiscretizationCollection(actx, mesh, order=4) sym_op = sym.normal(BTAG_ALL, mesh.dim) # sym_op = sym.nodes(mesh.dim, dd=BTAG_ALL) print(sym.pretty(sym_op)) op = bind(discr, sym_op) print() print(op.eval_code) vec = op(actx) vis = shortcuts.make_visualizer(discr) vis.write_vtk_file("geo.vtu", []) bvis = shortcuts.make_boundary_visualizer(discr) bvis.write_vtk_file("bgeo.vtu", [("normals", vec)])
def 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_geometric_factors_regular_refinement(actx_factory, name): from grudge.dt_utils import dt_geometric_factors actx = actx_factory() # {{{ cases if name == "interval": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=1) elif name == "box2d": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=2) elif name == "box3d": from mesh_data import BoxMeshBuilder builder = BoxMeshBuilder(ambient_dim=3) else: raise ValueError("unknown geometry name: %s" % name) # }}} min_factors = [] for resolution in builder.resolutions: mesh = builder.get_mesh(resolution, builder.mesh_order) dcoll = DiscretizationCollection(actx, mesh, order=builder.order) min_factors.append( actx.to_numpy( op.nodal_min(dcoll, "vol", thaw(dt_geometric_factors(dcoll), actx)))) # Resolution is doubled each refinement, so the ratio of consecutive # geometric factors should satisfy: gfi+1 / gfi = 2 min_factors = np.asarray(min_factors) ratios = min_factors[:-1] / min_factors[1:] assert np.all(np.isclose(ratios, 2))
def simple_mpi_communication_entrypoint(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis from meshmode.mesh import BTAG_ALL from mpi4py import MPI comm = MPI.COMM_WORLD num_parts = comm.Get_size() mesh_dist = MPIMeshDistributor(comm) if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-1, ) * 2, b=(1, ) * 2, nelements_per_axis=(2, ) * 2) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) else: local_mesh = mesh_dist.receive_mesh_part() vol_discr = DiscretizationCollection(actx, local_mesh, order=5, mpi_communicator=comm) sym_x = sym.nodes(local_mesh.dim) myfunc_symb = sym.sin(np.dot(sym_x, [2, 3])) myfunc = bind(vol_discr, myfunc_symb)(actx) sym_all_faces_func = sym.cse( sym.project("vol", "all_faces")(sym.var("myfunc"))) sym_int_faces_func = sym.cse( sym.project("vol", "int_faces")(sym.var("myfunc"))) sym_bdry_faces_func = sym.cse( sym.project(BTAG_ALL, "all_faces")(sym.project("vol", BTAG_ALL)(sym.var("myfunc")))) bound_face_swap = bind( vol_discr, sym.project("int_faces", "all_faces")( sym.OppositeInteriorFaceSwap("int_faces")(sym_int_faces_func)) - (sym_all_faces_func - sym_bdry_faces_func)) hopefully_zero = bound_face_swap(myfunc=myfunc) error = actx.np.linalg.norm(hopefully_zero, ord=np.inf) print(__file__) with np.printoptions(threshold=100000000, suppress=True): logger.debug(hopefully_zero) logger.info("error: %.5e", error) assert error < 1e-14
def test_operator_compiler_overwrite(actx_factory): """Tests that the same expression in ``eval_code`` and ``discr_code`` does not confuse the OperatorCompiler in grudge/symbolic/compiler.py. """ actx = actx_factory() ambient_dim = 2 target_order = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * ambient_dim, b=(0.5, ) * ambient_dim, n=(8, ) * ambient_dim, order=1) discr = DiscretizationCollection(actx, mesh, order=target_order) # {{{ test sym_u = sym.nodes(ambient_dim) sym_div_u = sum(d(u) for d, u in zip(sym.nabla(ambient_dim), sym_u)) div_u = bind(discr, sym_div_u)(actx) error = bind(discr, sym.norm(2, sym.var("x")))(actx, x=div_u - discr.dim) logger.info("error: %.5e", error)
def test_inverse_metric(actx_factory, dim): actx = actx_factory() mesh = mgen.generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(6, ) * dim, order=4) def m(x): result = np.empty_like(x) result[0] = (1.5 * x[0] + np.cos(x[0]) + 0.1 * np.sin(10 * x[1])) result[1] = (0.05 * np.cos(10 * x[0]) + 1.3 * x[1] + np.sin(x[1])) if len(x) == 3: result[2] = x[2] return result from meshmode.mesh.processing import map_mesh mesh = map_mesh(mesh, m) discr = DiscretizationCollection(actx, mesh, order=4) sym_op = (sym.forward_metric_derivative_mat(mesh.dim).dot( sym.inverse_metric_derivative_mat(mesh.dim)).reshape(-1)) op = bind(discr, sym_op) mat = op(actx).reshape(mesh.dim, mesh.dim) for i in range(mesh.dim): for j in range(mesh.dim): tgt = 1 if i == j else 0 err = actx.np.linalg.norm(mat[i, j] - tgt, ord=np.inf) logger.info("error[%d, %d]: %.5e", i, j, err) assert err < 1.0e-12, (i, j, err)
def test_norm_obj_array(actx_factory, p): """Test :func:`grudge.symbolic.operators.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) discr = DiscretizationCollection(actx, mesh, order=4) w = make_obj_array([1.0, 2.0, 3.0])[:dim] # {{ scalar sym_w = sym.var("w") norm = bind(discr, sym.norm(p, sym_w))(actx, w=w[0]) norm_exact = w[0] logger.info("norm: %.5e %.5e", norm, norm_exact) assert abs(norm - norm_exact) < 1.0e-14 # }}} # {{{ vector sym_w = sym.make_sym_array("w", dim) norm = bind(discr, sym.norm(p, sym_w))(actx, w=w) 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_incorrect_assignment_aggregation(actx_factory, ambient_dim): """Tests that the greedy assignemnt aggregation code works on a non-trivial expression (on which it didn't work at the time of writing). """ actx = actx_factory() target_order = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * ambient_dim, b=(0.5, ) * ambient_dim, n=(8, ) * ambient_dim, order=1) discr = DiscretizationCollection(actx, mesh, order=target_order) # {{{ test with a relative norm from grudge.dof_desc import DD_VOLUME dd = DD_VOLUME sym_x = sym.make_sym_array("y", ambient_dim, dd=dd) sym_y = sym.make_sym_array("y", ambient_dim, dd=dd) sym_norm_y = sym.norm(2, sym_y, dd=dd) sym_norm_d = sym.norm(2, sym_x - sym_y, dd=dd) sym_op = sym_norm_d / sym_norm_y logger.info("%s", sym.pretty(sym_op)) # FIXME: this shouldn't raise a RuntimeError with pytest.raises(RuntimeError): bind(discr, sym_op)(actx, x=1.0, y=discr.discr_from_dd(dd).nodes()) # }}} # {{{ test with repeated mass inverses sym_minv_y = sym.cse(sym.InverseMassOperator()(sym_y), "minv_y") sym_u = make_obj_array([0.5 * sym.Ones(dd), 0.0, 0.0])[:ambient_dim] sym_div_u = sum(d(u) for d, u in zip(sym.nabla(ambient_dim), sym_u)) sym_op = sym.MassOperator(dd)(sym_u) \ + sym.MassOperator(dd)(sym_minv_y * sym_div_u) logger.info("%s", sym.pretty(sym_op)) # FIXME: this shouldn't raise a RuntimeError either bind(discr, sym_op)(actx, y=discr.discr_from_dd(dd).nodes())
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_inverse_modal_connections_quadgrid(actx_factory): actx = actx_factory() order = 5 def f(x): return 1 + 2 * x + 3 * x**2 # Make a regular rectangle mesh mesh = mgen.generate_regular_rect_mesh( a=(0, 0), b=(5, 3), npoints_per_axis=(10, 6), order=order, group_cls=QuadratureSimplexGroupFactory.mesh_group_class) dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory={ dof_desc.DISCR_TAG_BASE: PolynomialWarpAndBlend2DRestrictingGroupFactory(order), dof_desc.DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2 * order) }) # Use dof descriptors on the quadrature grid dd_modal = dof_desc.DD_VOLUME_MODAL dd_quad = dof_desc.DOFDesc(dof_desc.DTAG_VOLUME_ALL, dof_desc.DISCR_TAG_QUAD) x_quad = thaw(dcoll.discr_from_dd(dd_quad).nodes()[0], actx) quad_f = f(x_quad) # Map nodal coefficients of f to modal coefficients forward_conn = dcoll.connection_from_dds(dd_quad, dd_modal) modal_f = forward_conn(quad_f) # Now map the modal coefficients back to nodal backward_conn = dcoll.connection_from_dds(dd_modal, dd_quad) quad_f_2 = backward_conn(modal_f) # This error should be small since we composed a map with # its inverse err = flat_norm(quad_f - quad_f_2) assert err <= 1e-11
def test_empty_boundary(actx_factory): # https://github.com/inducer/grudge/issues/54 from meshmode.mesh import BTAG_NONE actx = actx_factory() dim = 2 mesh = mgen.generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(8, ) * dim, order=4) discr = DiscretizationCollection(actx, mesh, order=4) normal = bind(discr, sym.normal(BTAG_NONE, dim, dim=dim - 1))(actx) from meshmode.dof_array import DOFArray for component in normal: assert isinstance(component, DOFArray) assert len(component) == len(discr.discr_from_dd(BTAG_NONE).groups)
def 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 gidx, grp_f in enumerate(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._volume_discr.groups[gidx].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)) from meshmode.dof_array import DOFArray, flat_norm 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 flat_norm(elem_mins - ref_mins, ord=np.inf) < 1.e-15 assert flat_norm(elem_maxs - ref_maxs, ord=np.inf) < 1.e-15 assert flat_norm(elem_sums - ref_sums, ord=np.inf) < 1.e-15
def _signed_face_ones(actx: ArrayContext, dcoll: DiscretizationCollection, dd) -> DOFArray: assert dd.is_trace() # NOTE: ignore quadrature_tags on dd, since we only care about # the face_id here all_faces_conn = dcoll.connection_from_dds(DD_VOLUME, DOFDesc(dd.domain_tag)) signed_face_ones = dcoll.discr_from_dd(dd).zeros( actx, dtype=dcoll.real_dtype) + 1 for igrp, grp in enumerate(all_faces_conn.groups): for batch in grp.batches: i = actx.thaw(batch.to_element_indices) grp_field = signed_face_ones[igrp].reshape(-1) grp_field[i] = \ (2.0 * (batch.to_element_face % 2) - 1.0) * grp_field[i] return signed_face_ones
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 mv_normal( actx: ArrayContext, dcoll: DiscretizationCollection, dd, ) -> MultiVector: """Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`. This supports both volume discretizations (where ambient == topological dimension) and surface discretizations (where ambient == topological dimension + 1). In the latter case, extra processing ensures that the returned normal is in the local tangent space of the element at the point where the normal is being evaluated. :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization. :returns: a :class:`~pymbolic.geometric_algebra.MultiVector` containing the unit normals. """ import grudge.dof_desc as dof_desc dd = dof_desc.as_dofdesc(dd) dim = dcoll.discr_from_dd(dd).dim ambient_dim = dcoll.ambient_dim if dim == ambient_dim: raise ValueError( "may only request normals on domains whose topological " f"dimension ({dim}) differs from " f"their ambient dimension ({ambient_dim})") if dim == ambient_dim - 1: return rel_mv_normal(actx, dcoll, dd=dd) # NOTE: In the case of (d - 2)-dimensional curves, we don't really have # enough information on the face to decide what an "exterior face normal" # is (e.g the "normal" to a 1D curve in 3D space is actually a # "normal plane") # # The trick done here is that we take the surface normal, move it to the # face and then take a cross product with the face tangent to get the # correct exterior face normal vector. assert dim == ambient_dim - 2 from grudge.op import project import grudge.dof_desc as dof_desc volm_normal = MultiVector( project( dcoll, dof_desc.DD_VOLUME, dd, rel_mv_normal(actx, dcoll, dd=dof_desc.DD_VOLUME).as_vector(dtype=object))) pder = pseudoscalar(actx, dcoll, dd=dd) mv = -(volm_normal ^ pder) << volm_normal.I.inv() return mv / actx.np.sqrt(mv.norm_squared())
def test_inverse_modal_connections(actx_factory, nodal_group_factory): actx = actx_factory() order = 4 def f(x): return 2 * actx.np.sin(20 * x) + 0.5 * actx.np.cos(10 * x) # Make a regular rectangle mesh mesh = mgen.generate_regular_rect_mesh( a=(0, 0), b=(5, 3), npoints_per_axis=(10, 6), order=order, group_cls=nodal_group_factory.mesh_group_class) dcoll = DiscretizationCollection(actx, mesh, discr_tag_to_group_factory={ dof_desc.DISCR_TAG_BASE: nodal_group_factory(order) }) dd_modal = dof_desc.DD_VOLUME_MODAL dd_volume = dof_desc.DD_VOLUME x_nodal = thaw(actx, dcoll.discr_from_dd(dd_volume).nodes()[0]) nodal_f = f(x_nodal) # Map nodal coefficients of f to modal coefficients forward_conn = dcoll.connection_from_dds(dd_volume, dd_modal) modal_f = forward_conn(nodal_f) # Now map the modal coefficients back to nodal backward_conn = dcoll.connection_from_dds(dd_modal, dd_volume) nodal_f_2 = backward_conn(modal_f) # This error should be small since we composed a map with # its inverse err = actx.np.linalg.norm(nodal_f - nodal_f_2) assert err <= 1e-13
def test_2d_gauss_theorem(actx_factory): """Verify Gauss's theorem explicitly on a mesh""" pytest.importorskip("meshpy") from meshpy.geometry import make_circle, GeometryBuilder from meshpy.triangle import MeshInfo, build geob = GeometryBuilder() geob.add_geometry(*make_circle(1)) mesh_info = MeshInfo() geob.set(mesh_info) mesh_info = build(mesh_info) from meshmode.mesh.io import from_meshpy from meshmode.mesh import BTAG_ALL mesh = from_meshpy(mesh_info, order=1) actx = actx_factory() dcoll = DiscretizationCollection(actx, mesh, order=2) volm_disc = dcoll.discr_from_dd(dof_desc.DD_VOLUME) x_volm = thaw(volm_disc.nodes(), actx) def f(x): return flat_obj_array( actx.np.sin(3 * x[0]) + actx.np.cos(3 * x[1]), actx.np.sin(2 * x[0]) + actx.np.cos(x[1])) f_volm = f(x_volm) int_1 = op.integral(dcoll, "vol", op.local_div(dcoll, f_volm)) prj_f = op.project(dcoll, "vol", BTAG_ALL, f_volm) normal = thaw(dcoll.normal(BTAG_ALL), actx) int_2 = op.integral(dcoll, BTAG_ALL, prj_f.dot(normal)) assert abs(int_1 - int_2) < 1e-13