diff --git a/doc/discretization.rst b/doc/discretization.rst index aa0fd8547f935cc93a8c4aa2ce5aada316a7f13f..d046d7ab90a3669138151c527686a86ab7061563 100644 --- a/doc/discretization.rst +++ b/doc/discretization.rst @@ -1,5 +1,5 @@ -Discretization -============== +Discretization Collection +========================= .. module:: grudge diff --git a/doc/geometry.rst b/doc/geometry.rst new file mode 100644 index 0000000000000000000000000000000000000000..d7047806b78d76c115cf9a7d04bed33d15511d00 --- /dev/null +++ b/doc/geometry.rst @@ -0,0 +1,4 @@ +Metric terms and transformations +================================ + +.. automodule:: grudge.geometry.metrics diff --git a/doc/index.rst b/doc/index.rst index 1e459c7ced1d66eb8c6379f7c1f9d83d2179441f..d113cce65741f2d97f0f89b9248587ab4a2a4c5a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,7 +8,8 @@ Contents: discretization dof_desc - symbolic + geometry + operators misc 🚀 Github <https://github.com/inducer/grudge> 💾 Download Releases <https://pypi.org/project/grudge> diff --git a/doc/operators.rst b/doc/operators.rst new file mode 100644 index 0000000000000000000000000000000000000000..a51d0f2a0e0e70c0989f5ac36c70d22323e0bc40 --- /dev/null +++ b/doc/operators.rst @@ -0,0 +1,5 @@ +Discontinuous Galerkin Operators +================================ + +.. automodule:: grudge.op +.. automodule:: grudge.trace_pair diff --git a/doc/symbolic.rst b/doc/symbolic.rst deleted file mode 100644 index 632b1f861beaa80c3f3c26e93b08272ec4a31259..0000000000000000000000000000000000000000 --- a/doc/symbolic.rst +++ /dev/null @@ -1,14 +0,0 @@ -Symbolic Operator Representation -================================ - -Based on :mod:`pymbolic`. - -Basic Objects -------------- - -.. automodule:: grudge.symbolic.primitives - -Operators ---------- - -.. automodule:: grudge.symbolic.operators diff --git a/examples/advection/surface.py b/examples/advection/surface.py index d528f5f3eee29366d9c963c3dbd5fe410e1819fa..74771af007aa2b3c1f124e0976140cdb4793e28e 100644 --- a/examples/advection/surface.py +++ b/examples/advection/surface.py @@ -1,4 +1,9 @@ -__copyright__ = "Copyright (C) 2020 Alexandru Fikl" +"""Minimal example of a grudge driver for DG on surfaces.""" + +__copyright__ = """ +Copyright (C) 2020 Alexandru Fikl +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,15 +29,17 @@ import os import numpy as np import pyopencl as cl +import pyopencl.tools as cl_tools -from meshmode.array_context import PyOpenCLArrayContext -from meshmode.dof_array import thaw, flatten +from arraycontext import PyOpenCLArrayContext, thaw + +from meshmode.dof_array import flatten from meshmode.discretization.connection import FACE_RESTR_INTERIOR -from grudge import bind, sym from pytools.obj_array import make_obj_array import grudge.dof_desc as dof_desc +import grudge.op as op import logging logger = logging.getLogger(__name__) @@ -41,10 +48,10 @@ logger = logging.getLogger(__name__) # {{{ plotting (keep in sync with `var-velocity.py`) class Plotter: - def __init__(self, actx, discr, order, visualize=True): + def __init__(self, actx, dcoll, order, visualize=True): self.actx = actx - self.ambient_dim = discr.ambient_dim - self.dim = discr.dim + self.ambient_dim = dcoll.ambient_dim + self.dim = dcoll.dim self.visualize = visualize if not self.visualize: @@ -54,11 +61,11 @@ class Plotter: import matplotlib.pyplot as pt self.fig = pt.figure(figsize=(8, 8), dpi=300) - x = thaw(actx, discr.discr_from_dd(dof_desc.DD_VOLUME).nodes()) - self.x = actx.to_numpy(flatten(actx.np.atan2(x[1], x[0]))) + x = thaw(dcoll.discr_from_dd(dof_desc.DD_VOLUME).nodes(), actx) + self.x = actx.to_numpy(flatten(actx.np.arctan2(x[1], x[0]))) elif self.ambient_dim == 3: from grudge.shortcuts import make_visualizer - self.vis = make_visualizer(discr) + self.vis = make_visualizer(dcoll) else: raise ValueError("unsupported dimension") @@ -94,10 +101,13 @@ class Plotter: # }}} -def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): +def main(ctx_factory, dim=2, order=4, use_quad=False, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) # {{{ parameters @@ -111,11 +121,6 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): # final time final_time = np.pi - # velocity field - sym_x = sym.nodes(dim) - c = make_obj_array([ - -sym_x[1], sym_x[0], 0.0 - ])[:dim] # flux flux_type = "lf" @@ -137,10 +142,10 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): raise ValueError("unsupported dimension") discr_tag_to_group_factory = {} - if product_tag == "none": - product_tag = None + if use_quad: + qtag = dof_desc.DISCR_TAG_QUAD else: - product_tag = dof_desc.DISCR_TAG_QUAD + qtag = None from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory, \ @@ -149,42 +154,51 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): discr_tag_to_group_factory[dof_desc.DISCR_TAG_BASE] = \ PolynomialWarpAndBlendGroupFactory(order) - if product_tag: - discr_tag_to_group_factory[product_tag] = \ + if use_quad: + discr_tag_to_group_factory[qtag] = \ QuadratureSimplexGroupFactory(order=4*order) from grudge import DiscretizationCollection - discr = DiscretizationCollection( + + dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory=discr_tag_to_group_factory ) - volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) + 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) # }}} - # {{{ symbolic operators + # {{{ Surface advection operator + + # velocity field + x = thaw(op.nodes(dcoll), actx) + c = make_obj_array([-x[1], x[0], 0.0])[:dim] def f_initial_condition(x): return x[0] from grudge.models.advection import SurfaceAdvectionOperator - op = SurfaceAdvectionOperator(c, + adv_operator = SurfaceAdvectionOperator( + dcoll, + c, flux_type=flux_type, - quad_tag=product_tag) + quad_tag=qtag + ) - bound_op = bind(discr, op.sym_operator()) - u0 = bind(discr, f_initial_condition(sym_x))(actx, t=0) + u0 = f_initial_condition(x) def rhs(t, u): - return bound_op(actx, t=t, u=u) + return adv_operator.operator(t, u) # check velocity is tangential - sym_normal = sym.surface_normal(dim, dim=dim - 1, - dd=dof_desc.DD_VOLUME).as_vector() - error = bind(discr, sym.norm(2, c.dot(sym_normal)))(actx) + from grudge.geometry import normal + + surf_normal = normal(actx, dcoll, dd=dof_desc.DD_VOLUME) + + error = op.norm(dcoll, c.dot(surf_normal), 2) logger.info("u_dot_n: %.5e", error) # }}} @@ -192,8 +206,7 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): # {{{ time stepping # compute time step - h_min = bind(discr, - sym.h_max_from_volume(discr.ambient_dim, dim=discr.dim))(actx) + h_min = op.h_max_from_volume(dcoll, dim=dcoll.dim) dt = dt_factor * h_min/order**2 nsteps = int(final_time // dt) + 1 dt = final_time/nsteps + 1.0e-15 @@ -203,10 +216,9 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u0, rhs) - plot = Plotter(actx, discr, order, visualize=visualize) + plot = Plotter(actx, dcoll, order, visualize=visualize) - norm = bind(discr, sym.norm(2, sym.var("u"))) - norm_u = norm(actx, u=u0) + norm_u = op.norm(dcoll, u0, 2) step = 0 @@ -215,17 +227,19 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): if visualize and dim == 3: from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr) - vis.write_vtk_file("fld-surface-velocity.vtu", [ - ("u", bind(discr, c)(actx)), - ("n", bind(discr, sym_normal)(actx)) - ], overwrite=True) + vis = make_visualizer(dcoll) + vis.write_vtk_file( + "fld-surface-velocity.vtu", + [ + ("u", c), + ("n", surf_normal) + ], + overwrite=True + ) df = dof_desc.DOFDesc(FACE_RESTR_INTERIOR) - face_discr = discr.connection_from_dds(dof_desc.DD_VOLUME, df).to_discr - - face_normal = bind(discr, sym.normal( - df, face_discr.ambient_dim, dim=face_discr.dim))(actx) + face_discr = dcoll.discr_from_dd(df) + face_normal = thaw(op.normal(dcoll, dd=df), actx) from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, face_discr) @@ -239,12 +253,14 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): step += 1 if step % 10 == 0: - norm_u = norm(actx, u=event.state_component) + norm_u = op.norm(dcoll, event.state_component, 2) plot(event, "fld-surface-%04d" % step) logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) - plot(event, "fld-surface-%04d" % step) + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified + assert norm_u < 3 # }}} @@ -254,10 +270,12 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--dim", choices=[2, 3], default=2, type=int) - parser.add_argument("--qtag", choices=["none", "product"], default="none") + parser.add_argument("--use-quad", action="store_false") + parser.add_argument("--visualize", action="store_true") args = parser.parse_args() logging.basicConfig(level=logging.INFO) main(cl.create_some_context, dim=args.dim, - product_tag=args.qtag) + use_quad=args.use_quad, + visualize=args.visualize) diff --git a/examples/advection/var-velocity.py b/examples/advection/var-velocity.py index 5843b99318cb65de77e53e9a78669a2c001dee2c..1d10c917d6ba03ec2922dcf6dfaa131c51a55aca 100644 --- a/examples/advection/var-velocity.py +++ b/examples/advection/var-velocity.py @@ -1,4 +1,9 @@ -__copyright__ = "Copyright (C) 2017 Bogdan Enache" +"""Minimal example of a grudge driver.""" + +__copyright__ = """ +Copyright (C) 2017 Bogdan Enache +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,15 +29,17 @@ import os import numpy as np import pyopencl as cl +import pyopencl.tools as cl_tools + +from arraycontext import PyOpenCLArrayContext, thaw -from meshmode.array_context import PyOpenCLArrayContext -from meshmode.dof_array import thaw, flatten +from meshmode.dof_array import flatten from meshmode.mesh import BTAG_ALL -from grudge import bind, sym from pytools.obj_array import flat_obj_array import grudge.dof_desc as dof_desc +import grudge.op as op import logging logger = logging.getLogger(__name__) @@ -41,9 +48,9 @@ logger = logging.getLogger(__name__) # {{{ plotting (keep in sync with `weak.py`) class Plotter: - def __init__(self, actx, discr, order, visualize=True, ylim=None): + def __init__(self, actx, dcoll, order, visualize=True, ylim=None): self.actx = actx - self.dim = discr.ambient_dim + self.dim = dcoll.ambient_dim self.visualize = visualize if not self.visualize: @@ -54,11 +61,11 @@ class Plotter: self.fig = pt.figure(figsize=(8, 8), dpi=300) self.ylim = ylim - volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) - self.x = actx.to_numpy(flatten(thaw(actx, volume_discr.nodes()[0]))) + volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) + self.x = actx.to_numpy(flatten(thaw(volume_discr.nodes()[0], actx))) else: from grudge.shortcuts import make_visualizer - self.vis = make_visualizer(discr) + self.vis = make_visualizer(dcoll) def __call__(self, evt, basename, overwrite=True): if not self.visualize: @@ -91,10 +98,13 @@ class Plotter: # }}} -def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): +def main(ctx_factory, dim=2, order=4, use_quad=False, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) # {{{ parameters @@ -116,16 +126,11 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): # flux flux_type = "upwind" - # velocity field - sym_x = sym.nodes(dim) - if dim == 1: - c = sym_x + + if use_quad: + qtag = dof_desc.DISCR_TAG_QUAD else: - # solid body rotation - c = flat_obj_array( - np.pi * (d/2 - sym_x[1]), - np.pi * (sym_x[0] - d/2), - 0)[:dim] + qtag = None # }}} @@ -140,51 +145,63 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory - if product_tag: + if use_quad: discr_tag_to_group_factory = { - product_tag: QuadratureSimplexGroupFactory(order=4*order) + qtag: QuadratureSimplexGroupFactory(order=4*order) } else: discr_tag_to_group_factory = {} from grudge import DiscretizationCollection - discr = DiscretizationCollection( + + dcoll = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory ) # }}} - # {{{ symbolic operators + # {{{ advection operator # gaussian parameters source_center = np.array([0.5, 0.75, 0.0])[:dim] source_width = 0.05 - dist_squared = np.dot(sym_x - source_center, sym_x - source_center) def f_gaussian(x): - return sym.exp(-dist_squared / source_width**2) + return actx.np.exp(-np.dot(x - source_center, + x - source_center) / source_width**2) - def f_step(x): - return sym.If(sym.Comparison( - dist_squared, "<", (4*source_width) ** 2), - 1, 0) - - def u_bc(x): - return 0.0 + def zero_inflow_bc(dtag, t=0): + dd = dof_desc.DOFDesc(dtag, qtag) + return dcoll.discr_from_dd(dd).zeros(actx) from grudge.models.advection import VariableCoefficientAdvectionOperator - op = VariableCoefficientAdvectionOperator( - c, - u_bc(sym.nodes(dim, BTAG_ALL)), - quad_tag=product_tag, - flux_type=flux_type) - bound_op = bind(discr, op.sym_operator()) - u = bind(discr, f_gaussian(sym.nodes(dim)))(actx, t=0) + x = thaw(op.nodes(dcoll), actx) + + # velocity field + if dim == 1: + c = x + else: + # solid body rotation + c = flat_obj_array( + np.pi * (d/2 - x[1]), + np.pi * (x[0] - d/2), + 0 + )[:dim] + + adv_operator = VariableCoefficientAdvectionOperator( + dcoll, + c, + inflow_u=lambda t: zero_inflow_bc(BTAG_ALL, t), + quad_tag=qtag, + flux_type=flux_type + ) + + u = f_gaussian(x) def rhs(t, u): - return bound_op(t=t, u=u) + return adv_operator.operator(t, u) # }}} @@ -192,22 +209,25 @@ def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=False): from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u, rhs) - plot = Plotter(actx, discr, order, visualize=visualize, + plot = Plotter(actx, dcoll, order, visualize=visualize, ylim=[-0.1, 1.1]) step = 0 - norm = bind(discr, sym.norm(2, sym.var("u"))) for event in dt_stepper.run(t_end=final_time): if not isinstance(event, dt_stepper.StateComputed): continue if step % 10 == 0: - norm_u = norm(u=event.state_component) + norm_u = op.norm(dcoll, event.state_component, 2) plot(event, "fld-var-velocity-%04d" % step) step += 1 logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified + assert norm_u < 1 + # }}} @@ -216,10 +236,10 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--dim", default=2, type=int) - parser.add_argument("--qtag", default="product") + parser.add_argument("--use-quad", action="store_false") args = parser.parse_args() logging.basicConfig(level=logging.INFO) main(cl.create_some_context, dim=args.dim, - product_tag=args.qtag) + use_quad=args.use_quad) diff --git a/examples/advection/weak.py b/examples/advection/weak.py index b92d0a46bff61149362ea9080d92580e5eb601d1..5c9ad1098033532c6782a2c1b730a5252358c2e3 100644 --- a/examples/advection/weak.py +++ b/examples/advection/weak.py @@ -1,4 +1,9 @@ -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" +"""Minimal example of a grudge driver.""" + +__copyright__ = """ +Copyright (C) 2007 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,14 +30,15 @@ import numpy as np import numpy.linalg as la import pyopencl as cl +import pyopencl.tools as cl_tools -from meshmode.array_context import PyOpenCLArrayContext -from meshmode.dof_array import thaw, flatten -from meshmode.mesh import BTAG_ALL +from arraycontext import PyOpenCLArrayContext, thaw -from grudge import bind, sym +from meshmode.dof_array import flatten +from meshmode.mesh import BTAG_ALL import grudge.dof_desc as dof_desc +import grudge.op as op import logging logger = logging.getLogger(__name__) @@ -41,9 +47,9 @@ logger = logging.getLogger(__name__) # {{{ plotting (keep in sync with `var-velocity.py`) class Plotter: - def __init__(self, actx, discr, order, visualize=True, ylim=None): + def __init__(self, actx, dcoll, order, visualize=True, ylim=None): self.actx = actx - self.dim = discr.ambient_dim + self.dim = dcoll.ambient_dim self.visualize = visualize if not self.visualize: @@ -54,11 +60,11 @@ class Plotter: self.fig = pt.figure(figsize=(8, 8), dpi=300) self.ylim = ylim - volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) - self.x = actx.to_numpy(flatten(thaw(actx, volume_discr.nodes()[0]))) + volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) + self.x = actx.to_numpy(flatten(thaw(volume_discr.nodes()[0], actx))) else: from grudge.shortcuts import make_visualizer - self.vis = make_visualizer(discr) + self.vis = make_visualizer(dcoll) def __call__(self, evt, basename, overwrite=True): if not self.visualize: @@ -95,7 +101,10 @@ class Plotter: def main(ctx_factory, dim=2, order=4, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) # {{{ parameters @@ -131,29 +140,36 @@ def main(ctx_factory, dim=2, order=4, visualize=False): order=order) from grudge import DiscretizationCollection - discr = DiscretizationCollection(actx, mesh, order=order) + + dcoll = DiscretizationCollection(actx, mesh, order=order) # }}} - # {{{ symbolic operators + # {{{ weak advection operator def f(x): - return sym.sin(3 * x) + return actx.np.sin(3 * x) - def u_analytic(x): - t = sym.var("t", dof_desc.DD_SCALAR) + def u_analytic(x, t=0): return f(-np.dot(c, x) / norm_c + t * norm_c) from grudge.models.advection import WeakAdvectionOperator - op = WeakAdvectionOperator(c, - inflow_u=u_analytic(sym.nodes(dim, BTAG_ALL)), - flux_type=flux_type) - bound_op = bind(discr, op.sym_operator()) - u = bind(discr, u_analytic(sym.nodes(dim)))(actx, t=0) + adv_operator = WeakAdvectionOperator( + dcoll, + c, + inflow_u=lambda t: u_analytic( + thaw(op.nodes(dcoll, dd=BTAG_ALL), actx), + t=t + ), + flux_type=flux_type + ) + + nodes = thaw(op.nodes(dcoll), actx) + u = u_analytic(nodes, t=0) def rhs(t, u): - return bound_op(t=t, u=u) + return adv_operator.operator(t, u) # }}} @@ -161,11 +177,9 @@ def main(ctx_factory, dim=2, order=4, visualize=False): from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u, rhs) - plot = Plotter(actx, discr, order, visualize=visualize, + plot = Plotter(actx, dcoll, order, visualize=visualize, ylim=[-1.1, 1.1]) - norm = bind(discr, sym.norm(2, sym.var("u"))) - step = 0 norm_u = 0.0 for event in dt_stepper.run(t_end=final_time): @@ -173,12 +187,16 @@ def main(ctx_factory, dim=2, order=4, visualize=False): continue if step % 10 == 0: - norm_u = norm(u=event.state_component) + norm_u = op.norm(dcoll, event.state_component, 2) plot(event, "fld-weak-%04d" % step) step += 1 logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified + assert norm_u < 1 + # }}} diff --git a/examples/geometry.py b/examples/geometry.py index 2af1d8cec58edfd0b2654862e1312b1e4d670893..faf0fc42c1553ea764dbc1621142e21c9f3f4599 100644 --- a/examples/geometry.py +++ b/examples/geometry.py @@ -1,6 +1,9 @@ -"""Minimal example of a grudge driver.""" +"""Minimal example of viewing geometric quantities.""" -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,39 +28,40 @@ THE SOFTWARE. import numpy as np # noqa import pyopencl as cl -from grudge import sym, bind, DiscretizationCollection, shortcuts +import pyopencl.tools as cl_tools + +from arraycontext import PyOpenCLArrayContext, thaw -from meshmode.array_context import PyOpenCLArrayContext +import grudge.op as op + +from grudge import DiscretizationCollection, shortcuts def main(write_output=True): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(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) + dcoll = DiscretizationCollection(actx, mesh, order=4) - vec = op(actx) + nodes = thaw(op.nodes(dcoll), actx) + bdry_nodes = thaw(op.nodes(dcoll, dd=BTAG_ALL), actx) + bdry_normals = thaw(op.normal(dcoll, dd=BTAG_ALL), actx) - vis = shortcuts.make_visualizer(discr) - vis.write_vtk_file("geo.vtu", [ - ]) + if write_output: + vis = shortcuts.make_visualizer(dcoll) + vis.write_vtk_file("geo.vtu", [("nodes", nodes)]) - bvis = shortcuts.make_boundary_visualizer(discr) - bvis.write_vtk_file("bgeo.vtu", [ - ("normals", vec) - ]) + bvis = shortcuts.make_boundary_visualizer(dcoll) + bvis.write_vtk_file("bgeo.vtu", [("bdry normals", bdry_normals), + ("bdry nodes", bdry_nodes)]) if __name__ == "__main__": diff --git a/examples/maxwell/cavities.py b/examples/maxwell/cavities.py index d82a5a5b6daf89b04ffccf6b92b0e9e5ea0ab7b8..57006e45d5c4499d3ea81bcea3781f93a457fa80 100644 --- a/examples/maxwell/cavities.py +++ b/examples/maxwell/cavities.py @@ -1,6 +1,9 @@ """Minimal example of a grudge driver.""" -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,22 +28,28 @@ THE SOFTWARE. import numpy as np import pyopencl as cl +import pyopencl.tools as cl_tools -from meshmode.array_context import PyOpenCLArrayContext +from arraycontext import PyOpenCLArrayContext, thaw from grudge.shortcuts import set_up_rk4 -from grudge import sym, bind, DiscretizationCollection +from grudge import DiscretizationCollection from grudge.models.em import get_rectangular_cavity_mode +import grudge.op as op + STEPS = 60 -def main(dims, write_output=True, order=4): +def main(dims, write_output=False, order=4): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( @@ -48,7 +57,7 @@ def main(dims, write_output=True, order=4): b=(1.0,)*dims, nelements_per_axis=(4,)*dims) - discr = DiscretizationCollection(actx, mesh, order=order) + dcoll = DiscretizationCollection(actx, mesh, order=order) if 0: epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) @@ -60,25 +69,30 @@ def main(dims, write_output=True, order=4): mu = 1 from grudge.models.em import MaxwellOperator - op = MaxwellOperator(epsilon, mu, flux_type=0.5, dimensions=dims) - if dims == 3: - sym_mode = get_rectangular_cavity_mode(1, (1, 2, 2)) - fields = bind(discr, sym_mode)(actx, t=0, epsilon=epsilon, mu=mu) - else: - sym_mode = get_rectangular_cavity_mode(1, (2, 3)) - fields = bind(discr, sym_mode)(actx, t=0) + maxwell_operator = MaxwellOperator( + dcoll, + epsilon, + mu, + flux_type=0.5, + dimensions=dims + ) - # FIXME - #dt = op.estimate_rk4_timestep(discr, fields=fields) + def cavity_mode(x, t=0): + if dims == 3: + return get_rectangular_cavity_mode(actx, x, t, 1, (1, 2, 2)) + else: + return get_rectangular_cavity_mode(actx, x, t, 1, (2, 3)) - op.check_bc_coverage(mesh) + fields = cavity_mode(thaw(op.nodes(dcoll), actx), t=0) - # print(sym.pretty(op.sym_operator())) - bound_op = bind(discr, op.sym_operator()) + # FIXME + # dt = maxwell_operator.estimate_rk4_timestep(dcoll, fields=fields) + + maxwell_operator.check_bc_coverage(mesh) def rhs(t, w): - return bound_op(t=t, w=w) + return maxwell_operator.operator(t, w) if mesh.dim == 2: dt = 0.004 @@ -93,23 +107,26 @@ def main(dims, write_output=True, order=4): print("dt=%g nsteps=%d" % (dt, nsteps)) from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr) + vis = make_visualizer(dcoll) step = 0 - norm = bind(discr, sym.norm(2, sym.var("u"))) + def norm(u): + return op.norm(dcoll, u, 2) from time import time t_last_step = time() - e, h = op.split_eh(fields) + e, h = maxwell_operator.split_eh(fields) - if 1: - vis.write_vtk_file("fld-cavities-%04d.vtu" % step, - [ - ("e", e), - ("h", h), - ]) + if write_output: + vis.write_vtk_file( + f"fld-cavities-{step:04d}.vtu", + [ + ("e", e), + ("h", h), + ] + ) for event in dt_stepper.run(t_end=final_t): if isinstance(event, dt_stepper.StateComputed): @@ -117,18 +134,32 @@ def main(dims, write_output=True, order=4): step += 1 - print(step, event.t, norm(u=e[0]), norm(u=e[1]), - norm(u=h[0]), norm(u=h[1]), - time()-t_last_step) + norm_e0 = norm(u=e[0]) + norm_e1 = norm(u=e[1]) + norm_h0 = norm(u=h[0]) + norm_h1 = norm(u=h[1]) + print(step, event.t, + norm_e0, norm_e1, norm_h0, norm_h1, + time()-t_last_step) if step % 10 == 0: - e, h = op.split_eh(event.state_component) - vis.write_vtk_file("fld-cavities-%04d.vtu" % step, + if write_output: + e, h = maxwell_operator.split_eh(event.state_component) + vis.write_vtk_file( + f"fld-cavities-{step:04d}.vtu", [ ("e", e), ("h", h), - ]) + ] + ) t_last_step = time() + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified + assert norm_e0 < 0.5 + assert norm_e1 < 0.5 + assert norm_h0 < 0.5 + assert norm_h1 < 0.5 + if __name__ == "__main__": main(3) diff --git a/examples/dagrt-fusion.py b/examples/old_symbolics/dagrt-fusion.py similarity index 97% rename from examples/dagrt-fusion.py rename to examples/old_symbolics/dagrt-fusion.py index bd399c3a38542280f49cb66b00d6979541085a31..ea6cb6bd6638f3da69a4b2ff3eb82115fb695c28 100755 --- a/examples/dagrt-fusion.py +++ b/examples/old_symbolics/dagrt-fusion.py @@ -54,13 +54,15 @@ import os import sys import pyopencl as cl import pyopencl.array # noqa +import pyopencl.tools as cl_tools import pytest import dagrt.language as lang import pymbolic.primitives as p +from arraycontext import PyOpenCLArrayContext + from meshmode.dof_array import DOFArray -from meshmode.array_context import PyOpenCLArrayContext import grudge.dof_desc as dof_desc import grudge.symbolic.mappers as gmap @@ -461,7 +463,7 @@ def get_wave_op_with_discr(actx, dims=2, order=4): discr = DiscretizationCollection(actx, mesh, order=order) - from grudge.models.wave import WeakWaveOperator + from symbolic_wave_op import WeakWaveOperator from meshmode.mesh import BTAG_ALL, BTAG_NONE op = WeakWaveOperator(0.1, dims, source_f=_get_source_term(dims), @@ -486,7 +488,10 @@ def get_wave_component(state_component): def test_stepper_equivalence(ctx_factory, order=4): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dims = 2 @@ -750,7 +755,10 @@ class ExecutionMapperWithMemOpCounting(ExecutionMapperWrapper): def test_assignment_memory_model(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) _, discr = get_wave_op_with_discr(actx, dims=2, order=3) @@ -778,7 +786,10 @@ def test_assignment_memory_model(ctx_factory): def test_stepper_mem_ops(ctx_factory, use_fusion): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dims = 2 @@ -949,7 +960,10 @@ def test_stepper_timing(ctx_factory, use_fusion): queue = cl.CommandQueue( cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dims = 3 @@ -1072,7 +1086,10 @@ else: def problem_stats(order=3): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) with open_output_file("grudge-problem-stats.txt") as outf: _, dg_discr_2d = get_wave_op_with_discr( @@ -1097,7 +1114,10 @@ def problem_stats(order=3): def statement_counts_table(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) fused_stepper = get_example_stepper(actx, use_fusion=True) stepper = get_example_stepper(actx, use_fusion=False) @@ -1188,7 +1208,10 @@ def mem_ops_results(actx, dims): def scalar_assignment_percent_of_total_mem_ops_table(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) result2d = mem_ops_results(actx, 2) result3d = mem_ops_results(actx, 3) diff --git a/examples/old_symbolics/symbolic_wave_op.py b/examples/old_symbolics/symbolic_wave_op.py new file mode 100644 index 0000000000000000000000000000000000000000..466c92a37c00e8639526eb4e1f9b5ad577123fd1 --- /dev/null +++ b/examples/old_symbolics/symbolic_wave_op.py @@ -0,0 +1,166 @@ +"""(Old world) symbolic wave equation operator.""" + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np +from meshmode.mesh import BTAG_ALL, BTAG_NONE +from grudge import sym +from pytools.obj_array import flat_obj_array + + +# {{{ Old-world symbolic wave operator + +class WeakWaveOperator: + r"""This operator discretizes the wave equation + :math:`\partial_t^2 u = c^2 \Delta u`. + + To be precise, we discretize the hyperbolic system + + .. math:: + + \partial_t u - c \\nabla \\cdot v = 0 + + \partial_t v - c \\nabla u = 0 + + The sign of :math:`v` determines whether we discretize the forward or the + backward wave equation. + + :math:`c` is assumed to be constant across all space. + """ + + def __init__(self, c, ambient_dim, source_f=0, + flux_type="upwind", + dirichlet_tag=BTAG_ALL, + dirichlet_bc_f=0, + neumann_tag=BTAG_NONE, + radiation_tag=BTAG_NONE): + assert isinstance(ambient_dim, int) + + self.c = c + self.ambient_dim = ambient_dim + self.source_f = source_f + + if self.c > 0: + self.sign = 1 + else: + self.sign = -1 + + self.dirichlet_tag = dirichlet_tag + self.neumann_tag = neumann_tag + self.radiation_tag = radiation_tag + + self.dirichlet_bc_f = dirichlet_bc_f + + self.flux_type = flux_type + + def flux(self, w): + u = w[0] + v = w[1:] + normal = sym.normal(w.dd, self.ambient_dim) + + central_flux_weak = -self.c*flat_obj_array( + np.dot(v.avg, normal), + u.avg * normal) + + if self.flux_type == "central": + return central_flux_weak + elif self.flux_type == "upwind": + return central_flux_weak - self.c*self.sign*flat_obj_array( + 0.5*(u.ext-u.int), + 0.5*(normal * np.dot(normal, v.ext-v.int))) + else: + raise ValueError("invalid flux type '%s'" % self.flux_type) + + def sym_operator(self): + d = self.ambient_dim + + w = sym.make_sym_array("w", d+1) + u = w[0] + v = w[1:] + + # boundary conditions ------------------------------------------------- + + # dirichlet BCs ------------------------------------------------------- + dir_u = sym.cse(sym.project("vol", self.dirichlet_tag)(u)) + dir_v = sym.cse(sym.project("vol", self.dirichlet_tag)(v)) + if self.dirichlet_bc_f: + # FIXME + from warnings import warn + warn("Inhomogeneous Dirichlet conditions on the wave equation " + "are still having issues.") + + dir_g = sym.var("dir_bc_u") + dir_bc = flat_obj_array(2*dir_g - dir_u, dir_v) + else: + dir_bc = flat_obj_array(-dir_u, dir_v) + + dir_bc = sym.cse(dir_bc, "dir_bc") + + # neumann BCs --------------------------------------------------------- + neu_u = sym.cse(sym.project("vol", self.neumann_tag)(u)) + neu_v = sym.cse(sym.project("vol", self.neumann_tag)(v)) + neu_bc = sym.cse(flat_obj_array(neu_u, -neu_v), "neu_bc") + + # radiation BCs ------------------------------------------------------- + rad_normal = sym.normal(self.radiation_tag, d) + + rad_u = sym.cse(sym.project("vol", self.radiation_tag)(u)) + rad_v = sym.cse(sym.project("vol", self.radiation_tag)(v)) + + rad_bc = sym.cse(flat_obj_array( + 0.5*(rad_u - self.sign*np.dot(rad_normal, rad_v)), + 0.5*rad_normal*(np.dot(rad_normal, rad_v) - self.sign*rad_u) + ), "rad_bc") + + # entire operator ----------------------------------------------------- + def flux(pair): + return sym.project(pair.dd, "all_faces")(self.flux(pair)) + + result = sym.InverseMassOperator()( + flat_obj_array( + -self.c*np.dot(sym.stiffness_t(self.ambient_dim), v), + -self.c*(sym.stiffness_t(self.ambient_dim)*u) + ) + + - sym.FaceMassOperator()(flux(sym.int_tpair(w)) + + flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc)) + + flux(sym.bv_tpair(self.neumann_tag, w, neu_bc)) + + flux(sym.bv_tpair(self.radiation_tag, w, rad_bc)) + + )) + + result[0] += self.source_f + + return result + + def check_bc_coverage(self, mesh): + from meshmode.mesh import check_bc_coverage + check_bc_coverage(mesh, [ + self.dirichlet_tag, + self.neumann_tag, + self.radiation_tag]) + +# }}} + + +# vim: foldmethod=marker diff --git a/examples/wave/var-propagation-speed.py b/examples/wave/var-propagation-speed.py index 5fb31617a2787de8f8334f709cf49f7a75f7d709..a1a211c923e1726ae80fcaee6cc584014585fb8f 100644 --- a/examples/wave/var-propagation-speed.py +++ b/examples/wave/var-propagation-speed.py @@ -1,6 +1,9 @@ """Minimal example of a grudge driver.""" -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,16 +28,25 @@ THE SOFTWARE. import numpy as np import pyopencl as cl +import pyopencl.tools as cl_tools + +from arraycontext import PyOpenCLArrayContext, thaw + from grudge.shortcuts import set_up_rk4 -from grudge import sym, bind, DiscretizationCollection +from grudge import DiscretizationCollection -from meshmode.array_context import PyOpenCLArrayContext +from pytools.obj_array import flat_obj_array +import grudge.op as op -def main(write_output=True, order=4): + +def main(write_output=False, order=4): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dims = 2 from meshmode.mesh.generation import generate_regular_rect_mesh @@ -43,45 +55,50 @@ def main(write_output=True, order=4): b=(0.5,)*dims, nelements_per_axis=(20,)*dims) - discr = DiscretizationCollection(actx, mesh, order=order) - - source_center = np.array([0.1, 0.22, 0.33])[:mesh.dim] - source_width = 0.05 - source_omega = 3 - - sym_x = sym.nodes(mesh.dim) - sym_source_center_dist = sym_x - source_center - sym_t = sym.ScalarVariable("t") - c = sym.If(sym.Comparison( - np.dot(sym_x, sym_x), "<", 0.15), - np.float32(0.1), np.float32(0.2)) + dcoll = DiscretizationCollection(actx, mesh, order=order) + + def source_f(actx, dcoll, t=0): + source_center = np.array([0.1, 0.22, 0.33])[:dcoll.dim] + source_width = 0.05 + source_omega = 3 + nodes = thaw(op.nodes(dcoll), actx) + source_center_dist = flat_obj_array( + [nodes[i] - source_center[i] for i in range(dcoll.dim)] + ) + return ( + np.sin(source_omega*t) + * actx.np.exp( + -np.dot(source_center_dist, source_center_dist) + / source_width**2 + ) + ) + + x = thaw(op.nodes(dcoll), actx) + ones = dcoll.zeros(actx) + 1 + c = actx.np.where(np.dot(x, x) < 0.15, 0.1 * ones, 0.2 * ones) from grudge.models.wave import VariableCoefficientWeakWaveOperator from meshmode.mesh import BTAG_ALL, BTAG_NONE - op = VariableCoefficientWeakWaveOperator(c, - discr.dim, - source_f=( - sym.sin(source_omega*sym_t) - * sym.exp( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2)), - dirichlet_tag=BTAG_NONE, - neumann_tag=BTAG_NONE, - radiation_tag=BTAG_ALL, - flux_type="upwind") - - from pytools.obj_array import flat_obj_array - fields = flat_obj_array(discr.zeros(actx), - [discr.zeros(actx) for i in range(discr.dim)]) - op.check_bc_coverage(mesh) + wave_op = VariableCoefficientWeakWaveOperator( + dcoll, + c, + source_f=source_f, + dirichlet_tag=BTAG_NONE, + neumann_tag=BTAG_NONE, + radiation_tag=BTAG_ALL, + flux_type="upwind" + ) - c_eval = bind(discr, c)(actx) + fields = flat_obj_array( + dcoll.zeros(actx), + [dcoll.zeros(actx) for i in range(dcoll.dim)] + ) - bound_op = bind(discr, op.sym_operator()) + wave_op.check_bc_coverage(mesh) def rhs(t, w): - return bound_op(t=t, w=w) + return wave_op.operator(t, w) if mesh.dim == 2: dt = 0.04 * 0.3 @@ -95,15 +112,28 @@ def main(write_output=True, order=4): print("dt=%g nsteps=%d" % (dt, nsteps)) from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr) + vis = make_visualizer(dcoll) step = 0 - norm = bind(discr, sym.norm(2, sym.var("u"))) + def norm(u): + return op.norm(dcoll, u, 2) from time import time t_last_step = time() + if write_output: + u = fields[0] + v = fields[1:] + vis.write_vtk_file( + f"fld-var-propogation-speed-{step:04d}.vtu", + [ + ("u", u), + ("v", v), + ("c", c), + ] + ) + for event in dt_stepper.run(t_end=final_t): if isinstance(event, dt_stepper.StateComputed): assert event.component_id == "w" @@ -113,13 +143,19 @@ def main(write_output=True, order=4): if step % 10 == 0: print(f"step: {step} t: {time()-t_last_step} " f"L2: {norm(u=event.state_component[0])}") - vis.write_vtk_file("fld-var-propogation-speed-%04d.vtu" % step, + if write_output: + vis.write_vtk_file( + f"fld-var-propogation-speed-{step:04d}.vtu", [ ("u", event.state_component[0]), ("v", event.state_component[1:]), - ("c", c_eval), - ]) + ("c", c), + ] + ) t_last_step = time() + + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified assert norm(u=event.state_component[0]) < 1 diff --git a/examples/wave/wave-min-mpi.py b/examples/wave/wave-min-mpi.py index 29f59dfff8342227638c6bdc503f7ad79d3251ed..1fb3eb9eaafb19bf1e576b9408985a609f8db467 100644 --- a/examples/wave/wave-min-mpi.py +++ b/examples/wave/wave-min-mpi.py @@ -1,6 +1,9 @@ """Minimal example of a grudge driver.""" -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,16 +28,27 @@ THE SOFTWARE. import numpy as np import pyopencl as cl -from meshmode.array_context import PyOpenCLArrayContext +import pyopencl.tools as cl_tools + +from arraycontext import PyOpenCLArrayContext, thaw + from grudge.shortcuts import set_up_rk4 -from grudge import sym, bind, DiscretizationCollection +from grudge import DiscretizationCollection + from mpi4py import MPI +from pytools.obj_array import flat_obj_array -def main(write_output=True, order=4): +import grudge.op as op + + +def main(write_output=False, order=4): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) comm = MPI.COMM_WORLD num_parts = comm.Get_size() @@ -61,7 +75,7 @@ def main(write_output=True, order=4): else: local_mesh = mesh_dist.receive_mesh_part() - discr = DiscretizationCollection(actx, local_mesh, order=order, + dcoll = DiscretizationCollection(actx, local_mesh, order=order, mpi_communicator=comm) if local_mesh.dim == 2: @@ -69,59 +83,79 @@ def main(write_output=True, order=4): elif local_mesh.dim == 3: dt = 0.02 - source_center = np.array([0.1, 0.22, 0.33])[:local_mesh.dim] - source_width = 0.05 - source_omega = 3 - - sym_x = sym.nodes(local_mesh.dim) - sym_source_center_dist = sym_x - source_center - sym_t = sym.ScalarVariable("t") + def source_f(actx, dcoll, t=0): + source_center = np.array([0.1, 0.22, 0.33])[:dcoll.dim] + source_width = 0.05 + source_omega = 3 + nodes = thaw(op.nodes(dcoll), actx) + source_center_dist = flat_obj_array( + [nodes[i] - source_center[i] for i in range(dcoll.dim)] + ) + return ( + np.sin(source_omega*t) + * actx.np.exp( + -np.dot(source_center_dist, source_center_dist) + / source_width**2 + ) + ) from grudge.models.wave import WeakWaveOperator from meshmode.mesh import BTAG_ALL, BTAG_NONE - op = WeakWaveOperator(0.1, discr.dim, - source_f=( - sym.sin(source_omega*sym_t) - * sym.exp( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2)), - dirichlet_tag=BTAG_NONE, - neumann_tag=BTAG_NONE, - radiation_tag=BTAG_ALL, - flux_type="upwind") - - from pytools.obj_array import flat_obj_array + + wave_op = WeakWaveOperator( + dcoll, + 0.1, + source_f=source_f, + dirichlet_tag=BTAG_NONE, + neumann_tag=BTAG_NONE, + radiation_tag=BTAG_ALL, + flux_type="upwind" + ) + fields = flat_obj_array( - discr.zeros(actx), - [discr.zeros(actx) for i in range(discr.dim)]) + dcoll.zeros(actx), + [dcoll.zeros(actx) for i in range(dcoll.dim)] + ) # FIXME - #dt = op.estimate_rk4_timestep(discr, fields=fields) - - op.check_bc_coverage(local_mesh) + # dt = wave_op.estimate_rk4_timestep(dcoll, fields=fields) - # print(sym.pretty(op.sym_operator())) - bound_op = bind(discr, op.sym_operator()) + wave_op.check_bc_coverage(local_mesh) def rhs(t, w): - return bound_op(t=t, w=w) + return wave_op.operator(t, w) dt_stepper = set_up_rk4("w", dt, fields, rhs) final_t = 10 nsteps = int(final_t/dt) - print("dt=%g nsteps=%d" % (dt, nsteps)) + + if comm.rank == 0: + print("dt=%g nsteps=%d" % (dt, nsteps)) from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr) + vis = make_visualizer(dcoll) step = 0 - norm = bind(discr, sym.norm(2, sym.var("u"))) + def norm(u): + return op.norm(dcoll, u, 2) from time import time t_last_step = time() + if write_output: + u = fields[0] + v = fields[1:] + vis.write_parallel_vtk_file( + comm, + f"fld-wave-min-mpi-{{rank:03d}}-{step:04d}.vtu", + [ + ("u", u), + ("v", v), + ] + ) + for event in dt_stepper.run(t_end=final_t): if isinstance(event, dt_stepper.StateComputed): assert event.component_id == "w" @@ -129,17 +163,21 @@ def main(write_output=True, order=4): step += 1 if step % 10 == 0: - if comm.rank == 0: - print(f"step: {step} t: {time()-t_last_step} " - f"L2: {norm(u=event.state_component[0])}") - vis.write_parallel_vtk_file( + print(f"step: {step} t: {time()-t_last_step} " + f"L2: {norm(u=event.state_component[0])}") + if write_output: + vis.write_parallel_vtk_file( comm, f"fld-wave-min-mpi-{{rank:03d}}-{step:04d}.vtu", [ ("u", event.state_component[0]), ("v", event.state_component[1:]), - ]) + ] + ) t_last_step = time() + + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified assert norm(u=event.state_component[0]) < 1 diff --git a/examples/wave/wave-min.py b/examples/wave/wave-min.py index bd306b0fbed822cf428504912b8e024965888150..a9947483b8bb8918c31972ebb519c9d9fedf613e 100644 --- a/examples/wave/wave-min.py +++ b/examples/wave/wave-min.py @@ -1,7 +1,9 @@ """Minimal example of a grudge driver.""" - -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,15 +28,25 @@ THE SOFTWARE. import numpy as np import pyopencl as cl -from meshmode.array_context import PyOpenCLArrayContext +import pyopencl.tools as cl_tools + +from arraycontext import PyOpenCLArrayContext, thaw + from grudge.shortcuts import set_up_rk4 -from grudge import sym, bind, DiscretizationCollection +from grudge import DiscretizationCollection + +from pytools.obj_array import flat_obj_array + +import grudge.op as op -def main(write_output=True, order=4): +def main(write_output=False, order=4): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dims = 2 from meshmode.mesh.generation import generate_regular_rect_mesh @@ -50,43 +62,49 @@ def main(write_output=True, order=4): print("%d elements" % mesh.nelements) - discr = DiscretizationCollection(actx, mesh, order=order) - - source_center = np.array([0.1, 0.22, 0.33])[:mesh.dim] - source_width = 0.05 - source_omega = 3 - - sym_x = sym.nodes(mesh.dim) - sym_source_center_dist = sym_x - source_center - sym_t = sym.ScalarVariable("t") + dcoll = DiscretizationCollection(actx, mesh, order=order) + + def source_f(actx, dcoll, t=0): + source_center = np.array([0.1, 0.22, 0.33])[:dcoll.dim] + source_width = 0.05 + source_omega = 3 + nodes = thaw(op.nodes(dcoll), actx) + source_center_dist = flat_obj_array( + [nodes[i] - source_center[i] for i in range(dcoll.dim)] + ) + return ( + np.sin(source_omega*t) + * actx.np.exp( + -np.dot(source_center_dist, source_center_dist) + / source_width**2 + ) + ) from grudge.models.wave import WeakWaveOperator from meshmode.mesh import BTAG_ALL, BTAG_NONE - op = WeakWaveOperator(0.1, discr.dim, - source_f=( - sym.sin(source_omega*sym_t) - * sym.exp( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2)), - dirichlet_tag=BTAG_NONE, - neumann_tag=BTAG_NONE, - radiation_tag=BTAG_ALL, - flux_type="upwind") - - from pytools.obj_array import flat_obj_array - fields = flat_obj_array(discr.zeros(actx), - [discr.zeros(actx) for i in range(discr.dim)]) - # FIXME - #dt = op.estimate_rk4_timestep(discr, fields=fields) + wave_op = WeakWaveOperator( + dcoll, + 0.1, + source_f=source_f, + dirichlet_tag=BTAG_NONE, + neumann_tag=BTAG_NONE, + radiation_tag=BTAG_ALL, + flux_type="upwind" + ) + + fields = flat_obj_array( + dcoll.zeros(actx), + [dcoll.zeros(actx) for i in range(dcoll.dim)] + ) - op.check_bc_coverage(mesh) + # FIXME + # dt = wave_op.estimate_rk4_timestep(dcoll, fields=fields) - # print(sym.pretty(op.sym_operator())) - bound_op = bind(discr, op.sym_operator()) + wave_op.check_bc_coverage(mesh) def rhs(t, w): - return bound_op(t=t, w=w) + return wave_op.operator(t, w) dt_stepper = set_up_rk4("w", dt, fields, rhs) @@ -95,15 +113,27 @@ def main(write_output=True, order=4): print("dt=%g nsteps=%d" % (dt, nsteps)) from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr) + vis = make_visualizer(dcoll) step = 0 - norm = bind(discr, sym.norm(2, sym.var("u"))) + def norm(u): + return op.norm(dcoll, u, 2) from time import time t_last_step = time() + if write_output: + u = fields[0] + v = fields[1:] + vis.write_vtk_file( + f"fld-wave-min-{step:04d}.vtu", + [ + ("u", u), + ("v", v), + ] + ) + for event in dt_stepper.run(t_end=final_t): if isinstance(event, dt_stepper.StateComputed): assert event.component_id == "w" @@ -113,12 +143,18 @@ def main(write_output=True, order=4): if step % 10 == 0: print(f"step: {step} t: {time()-t_last_step} " f"L2: {norm(u=event.state_component[0])}") - vis.write_vtk_file("fld-wave-min-%04d.vtu" % step, + if write_output: + vis.write_vtk_file( + f"fld-wave-min-{step:04d}.vtu", [ ("u", event.state_component[0]), ("v", event.state_component[1:]), - ]) + ] + ) t_last_step = time() + + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified assert norm(u=event.state_component[0]) < 1 diff --git a/examples/wave/wave-op-mpi.py b/examples/wave/wave-op-mpi.py index b6806b9d3375c4a2319366643bf9de431cf6c5e1..55253d17e04a723cbcec75e39b54856e46967aa5 100644 --- a/examples/wave/wave-op-mpi.py +++ b/examples/wave/wave-op-mpi.py @@ -1,4 +1,9 @@ -__copyright__ = "Copyright (C) 2020 Andreas Kloeckner" +"""Minimal example of a grudge driver.""" + +__copyright__ = """ +Copyright (C) 2020 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,18 +29,19 @@ THE SOFTWARE. import numpy as np import numpy.linalg as la # noqa import pyopencl as cl +import pyopencl.tools as cl_tools -from pytools.obj_array import flat_obj_array +from arraycontext import PyOpenCLArrayContext, thaw -from meshmode.array_context import PyOpenCLArrayContext -from meshmode.dof_array import thaw +from pytools.obj_array import flat_obj_array from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa from grudge.discretization import DiscretizationCollection -import grudge.op as op from grudge.shortcuts import make_visualizer -from grudge.symbolic.primitives import TracePair + +import grudge.op as op + from mpi4py import MPI @@ -45,7 +51,7 @@ def wave_flux(dcoll, c, w_tpair): u = w_tpair[0] v = w_tpair[1:] - normal = thaw(u.int.array_context, op.normal(dcoll, w_tpair.dd)) + normal = thaw(op.normal(dcoll, w_tpair.dd), u.int.array_context) flux_weak = flat_obj_array( np.dot(v.avg, normal), @@ -72,22 +78,27 @@ def wave_operator(dcoll, c, w): dir_bc = flat_obj_array(-dir_u, dir_v) return ( - op.inverse_mass(dcoll, - flat_obj_array( - -c*op.weak_local_div(dcoll, v), - -c*op.weak_local_grad(dcoll, u) - ) - + # noqa: W504 - op.face_mass(dcoll, - wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) - + wave_flux(dcoll, c=c, w_tpair=TracePair( - BTAG_ALL, interior=dir_bval, exterior=dir_bc)) - + sum( - wave_flux(dcoll, c=c, w_tpair=tpair) - for tpair in op.cross_rank_trace_pairs(dcoll, w)) - ) - ) + op.inverse_mass( + dcoll, + flat_obj_array( + -c*op.weak_local_div(dcoll, v), + -c*op.weak_local_grad(dcoll, u) + ) + + op.face_mass( + dcoll, + wave_flux( + dcoll, c=c, + w_tpair=op.bdry_trace_pair(dcoll, + BTAG_ALL, + interior=dir_bval, + exterior=dir_bc) + ) + sum( + wave_flux(dcoll, c=c, w_tpair=tpair) + for tpair in op.interior_trace_pairs(dcoll, w) ) + ) + ) + ) # }}} @@ -105,7 +116,7 @@ def bump(actx, dcoll, t=0): source_width = 0.05 source_omega = 3 - nodes = thaw(actx, op.nodes(dcoll)) + nodes = thaw(op.nodes(dcoll), actx) center_dist = flat_obj_array([ nodes[i] - source_center[i] for i in range(dcoll.dim) @@ -118,10 +129,13 @@ def bump(actx, dcoll, t=0): / source_width**2)) -def main(): +def main(write_output=False): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) comm = MPI.COMM_WORLD num_parts = comm.Get_size() @@ -182,19 +196,26 @@ def main(): if istep % 10 == 0: if comm.rank == 0: - print(f"step: {istep} t: {t} L2: {op.norm(dcoll, fields[0], 2)} " - f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])}") - vis.write_parallel_vtk_file( + print(f"step: {istep} t: {t} " + f"L2: {op.norm(dcoll, fields[0], 2)} " + f"Linf: {op.norm(dcoll, fields[0], np.inf)} " + f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])} " + f"sol min: {op.nodal_min(dcoll, 'vol', fields[0])}") + if write_output: + vis.write_parallel_vtk_file( comm, f"fld-wave-eager-mpi-{{rank:03d}}-{istep:04d}.vtu", [ ("u", fields[0]), ("v", fields[1:]), - ]) + ] + ) t += dt istep += 1 + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified assert op.norm(dcoll, fields[0], 2) < 1 diff --git a/examples/wave/wave-op-var-velocity.py b/examples/wave/wave-op-var-velocity.py index 6b7bed919e2d76fcb7f9465f84fec0efa3cd8e4f..7f842b809484444b64afe2b1aa7d0262e62dfcf5 100644 --- a/examples/wave/wave-op-var-velocity.py +++ b/examples/wave/wave-op-var-velocity.py @@ -1,4 +1,9 @@ -__copyright__ = "Copyright (C) 2020 Andreas Kloeckner" +"""Minimal example of a grudge driver.""" + +__copyright__ = """ +Copyright (C) 2020 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,19 +29,19 @@ THE SOFTWARE. import numpy as np import numpy.linalg as la # noqa import pyopencl as cl +import pyopencl.tools as cl_tools -from pytools.obj_array import flat_obj_array +from arraycontext import PyOpenCLArrayContext, thaw -from meshmode.array_context import PyOpenCLArrayContext -from meshmode.dof_array import thaw +from pytools.obj_array import flat_obj_array from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa from grudge.discretization import DiscretizationCollection from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD, DOFDesc -import grudge.op as op from grudge.shortcuts import make_visualizer -from grudge.symbolic.primitives import TracePair + +import grudge.op as op # {{{ wave equation bits @@ -48,7 +53,7 @@ def wave_flux(dcoll, c, w_tpair): u = w_tpair[0] v = w_tpair[1:] - normal = thaw(u.int.array_context, op.normal(dcoll, dd)) + normal = thaw(op.normal(dcoll, dd), u.int.array_context) flux_weak = flat_obj_array( np.dot(v.avg, normal), @@ -87,20 +92,28 @@ def wave_operator(dcoll, c, w): dd_allfaces_quad = DOFDesc("all_faces", DISCR_TAG_QUAD) return ( - op.inverse_mass(dcoll, - flat_obj_array( - -op.weak_local_div(dcoll, dd_quad, c_quad*v_quad), - -op.weak_local_grad(dcoll, dd_quad, c_quad*u_quad) \ - # pylint: disable=E1130 - ) - + # noqa: W504 - op.face_mass(dcoll, - dd_allfaces_quad, - wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) - + wave_flux(dcoll, c=c, w_tpair=TracePair( - BTAG_ALL, interior=dir_bval, exterior=dir_bc)) - )) + op.inverse_mass( + dcoll, + flat_obj_array( + -op.weak_local_div(dcoll, dd_quad, c_quad*v_quad), + -op.weak_local_grad(dcoll, dd_quad, c_quad*u_quad) \ + # pylint: disable=invalid-unary-operand-type + ) + op.face_mass( + dcoll, + dd_allfaces_quad, + wave_flux( + dcoll, c=c, + w_tpair=op.bdry_trace_pair(dcoll, + BTAG_ALL, + interior=dir_bval, + exterior=dir_bc) + ) + sum( + wave_flux(dcoll, c=c, w_tpair=tpair) + for tpair in op.interior_trace_pairs(dcoll, w) ) + ) + ) + ) # }}} @@ -120,7 +133,7 @@ def bump(actx, dcoll, t=0, width=0.05, center=None): center = center[:dcoll.dim] source_omega = 3 - nodes = thaw(actx, op.nodes(dcoll)) + nodes = thaw(op.nodes(dcoll), actx) center_dist = flat_obj_array([ nodes[i] - center[i] for i in range(dcoll.dim) @@ -133,10 +146,13 @@ def bump(actx, dcoll, t=0, width=0.05, center=None): / width**2)) -def main(): +def main(write_output=False): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dim = 2 nel_1d = 16 @@ -190,18 +206,26 @@ def main(): fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: - print(f"step: {istep} t: {t} L2: {op.norm(dcoll, fields[0], 2)} " - f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])}") - vis.write_vtk_file("fld-wave-eager-var-velocity-%04d.vtu" % istep, + print(f"step: {istep} t: {t} " + f"L2: {op.norm(dcoll, fields[0], 2)} " + f"Linf: {op.norm(dcoll, fields[0], np.inf)} " + f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])} " + f"sol min: {op.nodal_min(dcoll, 'vol', fields[0])}") + if write_output: + vis.write_vtk_file( + f"fld-wave-eager-var-velocity-{istep:04d}.vtu", [ ("c", c), ("u", fields[0]), ("v", fields[1:]), - ]) + ] + ) t += dt istep += 1 + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified assert op.norm(dcoll, fields[0], 2) < 1 diff --git a/examples/wave/wave-op.py b/examples/wave/wave-op.py index f8ef5785bb88b7b1b31adc0e4f81a4938d2187f7..0a669a2270b31202f398b487e03ad96d25fe6f26 100644 --- a/examples/wave/wave-op.py +++ b/examples/wave/wave-op.py @@ -1,4 +1,9 @@ -__copyright__ = "Copyright (C) 2020 Andreas Kloeckner" +"""Minimal example of a grudge driver.""" + +__copyright__ = """ +Copyright (C) 2020 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,18 +29,18 @@ THE SOFTWARE. import numpy as np import numpy.linalg as la # noqa import pyopencl as cl +import pyopencl.tools as cl_tools -from pytools.obj_array import flat_obj_array +from arraycontext import PyOpenCLArrayContext, thaw -from meshmode.array_context import PyOpenCLArrayContext -from meshmode.dof_array import thaw +from pytools.obj_array import flat_obj_array from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa from grudge.discretization import DiscretizationCollection -import grudge.op as op from grudge.shortcuts import make_visualizer -from grudge.symbolic.primitives import TracePair + +import grudge.op as op # {{{ wave equation bits @@ -44,7 +49,7 @@ def wave_flux(dcoll, c, w_tpair): u = w_tpair[0] v = w_tpair[1:] - normal = thaw(u.int.array_context, op.normal(dcoll, w_tpair.dd)) + normal = thaw(op.normal(dcoll, w_tpair.dd), u.int.array_context) flux_weak = flat_obj_array( np.dot(v.avg, normal), @@ -70,18 +75,28 @@ def wave_operator(dcoll, c, w): dir_bc = flat_obj_array(-dir_u, dir_v) return ( - op.inverse_mass(dcoll, - flat_obj_array( - -c*op.weak_local_div(dcoll, v), - -c*op.weak_local_grad(dcoll, u) - ) - + # noqa: W504 - op.face_mass(dcoll, - wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) - + wave_flux(dcoll, c=c, w_tpair=TracePair( - BTAG_ALL, interior=dir_bval, exterior=dir_bc)) - )) + op.inverse_mass( + dcoll, + flat_obj_array( + -c*op.weak_local_div(dcoll, v), + -c*op.weak_local_grad(dcoll, u) + ) + + op.face_mass( + dcoll, + sum( + wave_flux(dcoll, c=c, w_tpair=tpair) + for tpair in op.interior_trace_pairs(dcoll, w) + ) + + wave_flux( + dcoll, c=c, + w_tpair=op.bdry_trace_pair(dcoll, + BTAG_ALL, + interior=dir_bval, + exterior=dir_bc) ) + ) + ) + ) # }}} @@ -99,7 +114,7 @@ def bump(actx, dcoll, t=0): source_width = 0.05 source_omega = 3 - nodes = thaw(actx, op.nodes(dcoll)) + nodes = thaw(op.nodes(dcoll), actx) center_dist = flat_obj_array([ nodes[i] - source_center[i] for i in range(dcoll.dim) @@ -112,10 +127,13 @@ def bump(actx, dcoll, t=0): / source_width**2)) -def main(): +def main(write_output=False): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) - actx = PyOpenCLArrayContext(queue) + actx = PyOpenCLArrayContext( + queue, + allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)) + ) dim = 2 nel_1d = 16 @@ -157,17 +175,25 @@ def main(): fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: - print(f"step: {istep} t: {t} L2: {op.norm(dcoll, fields[0], 2)} " - f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])}") - vis.write_vtk_file("fld-wave-eager-%04d.vtu" % istep, + print(f"step: {istep} t: {t} " + f"L2: {op.norm(dcoll, fields[0], 2)} " + f"Linf: {op.norm(dcoll, fields[0], np.inf)} " + f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])} " + f"sol min: {op.nodal_min(dcoll, 'vol', fields[0])}") + if write_output: + vis.write_vtk_file( + f"fld-wave-eager-{istep:04d}.vtu", [ ("u", fields[0]), ("v", fields[1:]), - ]) + ] + ) t += dt istep += 1 + # NOTE: These are here to ensure the solution is bounded for the + # time interval specified assert op.norm(dcoll, fields[0], 2) < 1 diff --git a/grudge/discretization.py b/grudge/discretization.py index 2b86c994ec8fa58728f1bb0ef4df255f62549e20..47b425e632287ea702be0026b3646be484ff56da 100644 --- a/grudge/discretization.py +++ b/grudge/discretization.py @@ -1,4 +1,13 @@ -__copyright__ = "Copyright (C) 2015-2017 Andreas Kloeckner, Bogdan Enache" +""" +.. currentmodule:: grudge + +.. autoclass:: DiscretizationCollection +""" + +__copyright__ = """ +Copyright (C) 2015-2017 Andreas Kloeckner, Bogdan Enache +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,45 +30,54 @@ THE SOFTWARE. """ from pytools import memoize_method + from grudge.dof_desc import ( DISCR_TAG_BASE, DISCR_TAG_MODAL, DTAG_BOUNDARY, DOFDesc, as_dofdesc ) + import numpy as np # noqa: F401 -from meshmode.array_context import ArrayContext -from meshmode.discretization.connection import \ - FACE_RESTR_INTERIOR, FACE_RESTR_ALL, make_face_restriction -from meshmode.mesh import BTAG_PARTITION -from warnings import warn +from arraycontext import ArrayContext +from meshmode.discretization.connection import ( + FACE_RESTR_INTERIOR, + FACE_RESTR_ALL, + make_face_restriction +) +from meshmode.mesh import Mesh, BTAG_PARTITION -__doc__ = """ -.. autoclass:: DiscretizationCollection -""" +from warnings import warn class DiscretizationCollection: - """ - .. automethod :: __init__ + """A collection of discretizations, defined on the same underlying + :class:`~meshmode.mesh.Mesh`, corresponding to various mesh entities + (volume, interior facets, boundaries) and associated element + groups. - .. automethod :: discr_from_dd - .. automethod :: connection_from_dds + .. automethod:: __init__ - .. autoattribute :: dim - .. autoattribute :: ambient_dim - .. autoattribute :: mesh + .. autoattribute:: dim + .. autoattribute:: ambient_dim + .. autoattribute:: mesh + .. autoattribute:: real_dtype + .. autoattribute:: complex_dtype - .. automethod :: empty - .. automethod :: zeros + .. automethod:: discr_from_dd + .. automethod:: connection_from_dds + + .. automethod:: empty + .. automethod:: zeros """ - def __init__(self, array_context, mesh, order=None, - discr_tag_to_group_factory=None, mpi_communicator=None, - # FIXME: `quad_tag_to_group_factory` is deprecated - quad_tag_to_group_factory=None): + def __init__(self, array_context: ArrayContext, mesh: Mesh, + order=None, + discr_tag_to_group_factory=None, mpi_communicator=None, + # FIXME: `quad_tag_to_group_factory` is deprecated + quad_tag_to_group_factory=None): """ - :param discr_tag_to_group_factory: A mapping from discretization tags + :arg discr_tag_to_group_factory: A mapping from discretization tags (typically one of: :class:`grudge.dof_desc.DISCR_TAG_BASE`, :class:`grudge.dof_desc.DISCR_TAG_MODAL`, or :class:`grudge.dof_desc.DISCR_TAG_QUAD`) to a @@ -126,6 +144,7 @@ class DiscretizationCollection: self.group_factory_for_discretization_tag(DISCR_TAG_BASE) ) + # NOTE: Can be removed when symbolics are completely removed # {{{ management of discretization-scoped common subexpressions from pytools import UniqueNameGenerator @@ -197,6 +216,26 @@ class DiscretizationCollection: return boundary_connections def get_distributed_boundary_swap_connection(self, dd): + warn("`DiscretizationCollection.get_distributed_boundary_swap_connection` " + "is deprecated and will go away in 2022. Use " + "`DiscretizationCollection.distributed_boundary_swap_connection` " + "instead.", + DeprecationWarning, stacklevel=2) + return self.distributed_boundary_swap_connection(dd) + + def distributed_boundary_swap_connection(self, dd): + """Provides a mapping from the base volume discretization + to the exterior boundary restriction on a parallel boundary + partition described by *dd*. This connection is used to + communicate across element boundaries in different parallel + partitions during distributed runs. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. The domain tag must be a subclass + of :class:`grudge.dof_desc.DTAG_BOUNDARY` with an + associated :class:`meshmode.mesh.BTAG_PARTITION` + corresponding to a particular communication rank. + """ if dd.discretization_tag is not DISCR_TAG_BASE: # FIXME raise NotImplementedError( @@ -211,6 +250,12 @@ class DiscretizationCollection: @memoize_method def discr_from_dd(self, dd): + """Provides a :class:`meshmode.discretization.Discretization` + object from *dd*. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. + """ dd = as_dofdesc(dd) discr_tag = dd.discretization_tag @@ -246,6 +291,16 @@ class DiscretizationCollection: @memoize_method def connection_from_dds(self, from_dd, to_dd): + """Provides a mapping (connection) from one discretization to + another, e.g. from the volume to the boundary, or from the + base to the an overintegrated quadrature discretization, or from + a nodal representation to a modal representation. + + :arg from_dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. + :arg to_dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. + """ from_dd = as_dofdesc(from_dd) to_dd = as_dofdesc(to_dd) @@ -453,6 +508,10 @@ class DiscretizationCollection: @memoize_method def opposite_face_connection(self): + """Provides a mapping from the base volume discretization + to the exterior boundary restriction on a neighboring element. + This does not take into account parallel partitions. + """ from meshmode.discretization.connection import \ make_opposite_face_connection @@ -482,28 +541,51 @@ class DiscretizationCollection: @property def dim(self): + """Return the topological dimension.""" return self._volume_discr.dim @property def ambient_dim(self): + """Return the dimension of the ambient space.""" return self._volume_discr.ambient_dim @property def real_dtype(self): + """Return the data type used for real-valued arithmetic.""" return self._volume_discr.real_dtype @property def complex_dtype(self): + """Return the data type used for complex-valued arithmetic.""" return self._volume_discr.complex_dtype @property def mesh(self): + """Return the :class:`meshmode.mesh.Mesh` over which the discretization + collection is built. + """ return self._volume_discr.mesh def empty(self, array_context: ArrayContext, dtype=None): + """Return an empty :class:`~meshmode.dof_array.DOFArray` defined at + the volume nodes: :class:`grudge.dof_desc.DD_VOLUME`. + + :arg array_context: an :class:`~arraycontext.context.ArrayContext`. + :arg dtype: type special value 'c' will result in a + vector of dtype :attr:`complex_dtype`. If + *None* (the default), a real vector will be returned. + """ return self._volume_discr.empty(array_context, dtype) def zeros(self, array_context: ArrayContext, dtype=None): + """Return a zero-initialized :class:`~meshmode.dof_array.DOFArray` + defined at the volume nodes, :class:`grudge.dof_desc.DD_VOLUME`. + + :arg array_context: an :class:`~arraycontext.context.ArrayContext`. + :arg dtype: type special value 'c' will result in a + vector of dtype :attr:`complex_dtype`. If + *None* (the default), a real vector will be returned. + """ return self._volume_discr.zeros(array_context, dtype) def is_volume_where(self, where): diff --git a/grudge/execution.py b/grudge/execution.py index 4f62a1d378af65c94f7dfb1dc9fde7199680fe0d..73b5e894839a58c9275781410353f034182fc132 100644 --- a/grudge/execution.py +++ b/grudge/execution.py @@ -20,6 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + +from arraycontext import ArrayContext, make_loopy_program, thaw + from typing import Optional, Union, Dict from numbers import Number import numpy as np @@ -29,8 +32,7 @@ from pytools import memoize_in import loopy as lp import pyopencl.array # noqa -from meshmode.dof_array import DOFArray, thaw, flatten, unflatten -from meshmode.array_context import ArrayContext, make_loopy_program +from meshmode.dof_array import DOFArray, flatten, unflatten import grudge.symbolic.mappers as mappers from grudge import sym @@ -72,15 +74,18 @@ class ExecutionMapper(mappers.Evaluator, def map_node_coordinate_component(self, expr): discr = self.dcoll.discr_from_dd(expr.dd) - return thaw(self.array_context, discr.nodes( - # only save volume nodes or boundary nodes - # (but not nodes for interior face discretizations, which are likely only - # used once to compute the normals) - cached=( - discr.ambient_dim == discr.dim - or expr.dd.is_boundary_or_partition_interface() + return thaw( + discr.nodes( + # only save volume nodes or boundary nodes + # (but not nodes for interior face discretizations, which + # are likely only used once to compute the normals) + cached=( + discr.ambient_dim == discr.dim + or expr.dd.is_boundary_or_partition_interface() ) - )[expr.axis]) + )[expr.axis], + self.array_context + ) def map_grudge_variable(self, expr): from numbers import Number @@ -313,7 +318,7 @@ class ExecutionMapper(mappers.Evaluator, def map_opposite_partition_face_swap(self, op, field_expr): assert op.dd_in == op.dd_out - bdry_conn = self.dcoll.get_distributed_boundary_swap_connection(op.dd_in) + bdry_conn = self.dcoll.distributed_boundary_swap_connection(op.dd_in) remote_bdry_vec = self.rec(field_expr) # swapped by RankDataSwapAssign return bdry_conn(remote_bdry_vec) diff --git a/grudge/geometry/__init__.py b/grudge/geometry/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fec1e5958425562a0070b988a11e10dd2b9e1f36 --- /dev/null +++ b/grudge/geometry/__init__.py @@ -0,0 +1,62 @@ +__copyright__ = """ +Copyright (C) 2021 University of Illinois Board of Trustees +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +from grudge.geometry.metrics import ( + forward_metric_nth_derivative, + forward_metric_derivative_mat, + inverse_metric_derivative_mat, + + first_fundamental_form, + inverse_first_fundamental_form, + + inverse_surface_metric_derivative, + pseudoscalar, + area_element, + + normal, + + second_fundamental_form, + shape_operator, + summed_curvature +) + +__all__ = ( + "forward_metric_nth_derivative", + "forward_metric_derivative_mat", + "inverse_metric_derivative_mat", + + "first_fundamental_form", + "inverse_first_fundamental_form", + + "inverse_surface_metric_derivative", + "pseudoscalar", + "area_element", + + "normal", + + "second_fundamental_form", + "shape_operator", + "summed_curvature", +) diff --git a/grudge/geometry/metrics.py b/grudge/geometry/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..fe0030dd31e90e7702292e9d4c83901b3db37516 --- /dev/null +++ b/grudge/geometry/metrics.py @@ -0,0 +1,713 @@ +""" +.. currentmodule:: grudge.geometry + +Coordinate transformations +-------------------------- + +.. autofunction:: forward_metric_nth_derivative +.. autofunction:: forward_metric_derivative_mat +.. autofunction:: inverse_metric_derivative_mat + +.. autofunction:: first_fundamental_form +.. autofunction:: inverse_first_fundamental_form + +Geometry terms +-------------- + +.. autofunction:: inverse_surface_metric_derivative +.. autofunction:: pseudoscalar +.. autofunction:: area_element + +Normal vectors +-------------- + +.. autofunction:: normal + +Curvature tensors +----------------- + +.. autofunction:: second_fundamental_form +.. autofunction:: shape_operator +.. autofunction:: summed_curvature +""" + +__copyright__ = """ +Copyright (C) 2021 University of Illinois Board of Trustees +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np + +from grudge import DiscretizationCollection +from arraycontext import thaw, freeze, ArrayContext +from meshmode.dof_array import DOFArray + +from grudge.dof_desc import ( + DD_VOLUME, DOFDesc, DISCR_TAG_BASE +) + +from pymbolic.geometric_algebra import MultiVector + +from pytools.obj_array import make_obj_array +from pytools import memoize_in + + +# {{{ Metric computations + +def forward_metric_nth_derivative( + actx: ArrayContext, dcoll: DiscretizationCollection, + xyz_axis, ref_axes, dd=None) -> DOFArray: + r"""Pointwise metric derivatives representing repeated derivatives of the + physical coordinate enumerated by *xyz_axis*: :math:`x_{\mathrm{xyz\_axis}}` + with respect to the coordiantes on the reference element :math:`\xi_i`: + + .. math:: + + D^\alpha x_{\mathrm{xyz\_axis}} = + \frac{\partial^{|\alpha|} x_{\mathrm{xyz\_axis}} }{ + \partial \xi_1^{\alpha_1}\cdots \partial \xi_m^{\alpha_m}} + + where :math:`\alpha` is a multi-index described by *ref_axes*. + + :arg xyz_axis: an integer denoting which physical coordinate to + differentiate. + :arg ref_axes: a :class:`tuple` of tuples indicating indices of + coordinate axes of the reference element to the number of derivatives + which will be taken. For example, the value ``((0, 2), (1, 1))`` + indicates taking the second derivative with respect to the first + axis and the first derivative with respect to the second + axis. Each axis must occur only once and the tuple must be sorted + by the axis index. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization. + :returns: a :class:`~meshmode.dof_array.DOFArray` containing the pointwise + metric derivative at each nodal coordinate. + """ + if dd is None: + dd = DD_VOLUME + + inner_dd = dd.with_discr_tag(DISCR_TAG_BASE) + + if isinstance(ref_axes, int): + ref_axes = ((ref_axes, 1),) + + if not isinstance(ref_axes, tuple): + raise ValueError("ref_axes must be a tuple") + + if tuple(sorted(ref_axes)) != ref_axes: + raise ValueError("ref_axes must be sorted") + + if len(set(ref_axes)) != len(ref_axes): + raise ValueError("ref_axes must not contain an axis more than once") + + from pytools import flatten + flat_ref_axes = flatten([rst_axis] * n for rst_axis, n in ref_axes) + + from meshmode.discretization import num_reference_derivative + + vec = num_reference_derivative( + dcoll.discr_from_dd(inner_dd), + flat_ref_axes, + thaw(dcoll.discr_from_dd(inner_dd).nodes(), actx)[xyz_axis] + ) + + if dd.uses_quadrature(): + vec = dcoll.connection_from_dds(inner_dd, dd)(vec) + + return vec + + +def forward_metric_derivative_vector( + actx: ArrayContext, dcoll: DiscretizationCollection, rst_axis, dd=None + ) -> np.ndarray: + r"""Computes an object array containing the forward metric derivatives + of each physical coordinate. + + :arg rst_axis: a :class:`tuple` of tuples indicating indices of + coordinate axes of the reference element to the number of derivatives + which will be taken. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization. + :returns: an object array of :class:`~meshmode.dof_array.DOFArray`\ s + containing the pointwise metric derivatives at each nodal coordinate. + """ + return make_obj_array([ + forward_metric_nth_derivative(actx, dcoll, i, rst_axis, dd=dd) + for i in range(dcoll.ambient_dim) + ] + ) + + +def forward_metric_derivative_mv( + actx: ArrayContext, dcoll: DiscretizationCollection, rst_axis, dd=None + ) -> MultiVector: + r"""Computes a :class:`pymbolic.geometric_algebra.MultiVector` containing + the forward metric derivatives of each physical coordinate. + + :arg rst_axis: a :class:`tuple` of tuples indicating indices of + coordinate axes of the reference element to the number of derivatives + which will be taken. + :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 forward metric derivatives in each physical coordinate. + """ + return MultiVector( + forward_metric_derivative_vector(actx, dcoll, rst_axis, dd=dd) + ) + + +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 first_fundamental_form(actx: ArrayContext, dcoll: DiscretizationCollection, + dd=None) -> np.ndarray: + r"""Computes the first fundamental form using the Jacobian matrix: + + .. math:: + + \begin{bmatrix} + E & F \\ F & G + \end{bmatrix} := + \begin{bmatrix} + (\partial_u x)^2 & \partial_u x \partial_v x \\ + \partial_u x \partial_v x & (\partial_v x)^2 + \end{bmatrix} = + J^T \cdot J + + where :math:`u, v` are coordinates on the parameterized surface and + :math:`x(u, v)` defines a parameterized region. Here, :math:`J` is the + corresponding Jacobian 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 coefficients of the first fundamental + form. + """ + if dd is None: + dd = DD_VOLUME + + mder = forward_metric_derivative_mat(actx, dcoll, dd=dd) + + return mder.T.dot(mder) + + +def inverse_metric_derivative_mat( + actx: ArrayContext, dcoll: DiscretizationCollection, dd=None + ) -> np.ndarray: + r"""Computes the inverse metric derivative matrix, which is + the inverse of the Jacobian (forward metric derivative) 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 inverse metric derivatives. + """ + 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 i in range(dim): + for j in range(ambient_dim): + result[i, j] = inverse_metric_derivative( + actx, dcoll, i, j, dd=dd + ) + + return result + + +def inverse_first_fundamental_form( + actx: ArrayContext, dcoll: DiscretizationCollection, dd=None + ) -> np.ndarray: + r"""Computes the inverse of the first fundamental form: + + .. math:: + + \begin{bmatrix} + E & F \\ F & G + \end{bmatrix}^{-1} = + \frac{1}{E G - F^2} + \begin{bmatrix} + G & -F \\ -F & E + \end{bmatrix} + + where :math:`E, F, G` are coefficients of the first fundamental form. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization. + :returns: a matrix containing coefficients of the inverse of the + first fundamental form. + """ + if dd is None: + dd = DD_VOLUME + + dim = dcoll.discr_from_dd(dd).dim + + if dcoll.ambient_dim == dim: + inv_mder = inverse_metric_derivative_mat(actx, dcoll, dd=dd) + inv_form1 = inv_mder.dot(inv_mder.T) + else: + form1 = first_fundamental_form(actx, dcoll, dd=dd) + + if dim == 1: + inv_form1 = 1.0 / form1 + elif dim == 2: + (E, F), (_, G) = form1 # noqa: N806 + inv_form1 = 1.0 / (E * G - F * F) * np.stack( + [make_obj_array([G, -F]), + make_obj_array([-F, E])] + ) + else: + raise ValueError(f"{dim}D surfaces not supported" % dim) + + return inv_form1 + + +def inverse_metric_derivative( + actx: ArrayContext, dcoll: DiscretizationCollection, rst_axis, xyz_axis, dd + ) -> DOFArray: + r"""Computes the inverse metric derivative of the physical + coordinate enumerated by *xyz_axis* with respect to the + reference axis *rst_axis*. + + :arg rst_axis: an integer denoting the reference coordinate axis. + :arg xyz_axis: an integer denoting the physical coordinate axis. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization. + :returns: a :class:`~meshmode.dof_array.DOFArray` containing the + inverse metric derivative at each nodal coordinate. + """ + + dim = dcoll.dim + if dim != dcoll.ambient_dim: + raise ValueError( + "Not clear what inverse_metric_derivative means if " + "the derivative matrix is not square!" + ) + + par_vecs = [forward_metric_derivative_mv(actx, dcoll, rst, dd) + for rst in range(dim)] + + # Yay Cramer's rule! + from functools import reduce, partial + from operator import xor as outerprod_op + outerprod = partial(reduce, outerprod_op) + + def outprod_with_unit(i, at): + unit_vec = np.zeros(dim) + unit_vec[i] = 1 + + vecs = par_vecs[:] + vecs[at] = MultiVector(unit_vec) + + return outerprod(vecs) + + volume_pseudoscalar_inv = outerprod( + forward_metric_derivative_mv(actx, dcoll, rst_axis, dd) + for rst_axis in range(dim) + ).inv() + + result = (outprod_with_unit(xyz_axis, rst_axis) + * volume_pseudoscalar_inv).as_scalar() + + return result + + +def inverse_surface_metric_derivative( + actx: ArrayContext, dcoll: DiscretizationCollection, + rst_axis, xyz_axis, dd=None): + r"""Computes the inverse surface metric derivative of the physical + coordinate enumerated by *xyz_axis* with respect to the + reference axis *rst_axis*. These geometric terms are used in the + transformation of physical gradients. + + :arg rst_axis: an integer denoting the reference coordinate axis. + :arg xyz_axis: an integer denoting the physical coordinate axis. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization. + :returns: a :class:`~meshmode.dof_array.DOFArray` containing the + inverse metric derivative at each nodal coordinate. + """ + dim = dcoll.dim + ambient_dim = dcoll.ambient_dim + + @memoize_in(dcoll, (inverse_surface_metric_derivative, dd, + "inv_metric_deriv_rst%s_xyz%s_adim%s_gdim%s" + % (rst_axis, xyz_axis, ambient_dim, dim))) + def _inv_surf_metric_deriv(): + if ambient_dim == dim: + imd = inverse_metric_derivative( + actx, dcoll, rst_axis, xyz_axis, dd=dd + ) + else: + inv_form1 = inverse_first_fundamental_form(actx, dcoll, dd=dd) + imd = sum( + inv_form1[rst_axis, d]*forward_metric_nth_derivative( + actx, dcoll, xyz_axis, d, dd=dd + ) for d in range(dim) + ) + return freeze(imd, actx) + return thaw(_inv_surf_metric_deriv(), actx) + + +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 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 pseudoscalar(actx: ArrayContext, dcoll: DiscretizationCollection, + dd=None) -> MultiVector: + r"""Computes the field of pseudoscalars for the domain/discretization + identified by *dd*. + + :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` of + :class:`~meshmode.dof_array.DOFArray`\ s. + """ + if dd is None: + dd = DD_VOLUME + + return parametrization_derivative(actx, dcoll, dd).project_max_grade() + + +def area_element( + actx: ArrayContext, dcoll: DiscretizationCollection, dd=None + ) -> DOFArray: + r"""Computes the scale factor used to transform integrals from reference + to global space. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization. + :returns: a :class:`~meshmode.dof_array.DOFArray` containing the transformed + volumes for each element. + """ + if dd is None: + dd = DD_VOLUME + + dim = dcoll.discr_from_dd(dd).dim + + @memoize_in(dcoll, (area_element, dd, + "area_elements_adim%s_gdim%s" + % (dcoll.ambient_dim, dim))) + def _area_elements(): + return freeze(actx.np.sqrt( + pseudoscalar(actx, dcoll, dd=dd).norm_squared()), actx) + + return thaw(_area_elements(), actx) + +# }}} + + +# {{{ Surface normal vectors + +def rel_mv_normal( + actx: ArrayContext, dcoll: DiscretizationCollection, dd=None) -> MultiVector: + r"""Computes surface normals at each nodal location as a + :class:`~pymbolic.geometric_algebra.MultiVector` relative to the + pseudoscalar of the discretization described by *dd*. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + """ + import grudge.dof_desc as dof_desc + + dd = dof_desc.as_dofdesc(dd) + + # NOTE: Don't be tempted to add a sign here. As it is, it produces + # exterior normals for positively oriented curves. + + pder = pseudoscalar(actx, dcoll, dd=dd) / area_element(actx, dcoll, dd=dd) + + # Dorst Section 3.7.2 + return pder << pder.I.inv() + + +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 normal(actx: ArrayContext, dcoll: DiscretizationCollection, dd): + """Get the unit normal to the specified surface discretization, *dd*. + 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: an object array of :class:`~meshmode.dof_array.DOFArray` + containing the unit normals at each nodal location. + """ + return mv_normal(actx, dcoll, dd).as_vector(dtype=object) + +# }}} + + +# {{{ Curvature computations + +def second_fundamental_form( + actx: ArrayContext, dcoll: DiscretizationCollection, dd=None + ) -> np.ndarray: + r"""Computes the second fundamental form: + + .. math:: + + S(x) = \begin{bmatrix} + \partial_{uu} x\cdot n & \partial_{uv} x\cdot n \\ + \partial_{uv} x\cdot n & \partial_{vv} x\cdot n + \end{bmatrix} + + where :math:`n` is the surface normal, :math:`x(u, v)` defines a parameterized + surface, and :math:`u,v` are coordinates on the parameterized surface. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + :returns: a rank-2 object array describing second fundamental form. + """ + + if dd is None: + dd = DD_VOLUME + + dim = dcoll.discr_from_dd(dd).dim + normal = rel_mv_normal(actx, dcoll, dd=dd).as_vector(dtype=object) + + if dim == 1: + second_ref_axes = [((0, 2),)] + elif dim == 2: + second_ref_axes = [((0, 2),), ((0, 1), (1, 1)), ((1, 2),)] + else: + raise ValueError("%dD surfaces not supported" % dim) + + from pytools import flatten + + form2 = np.empty((dim, dim), dtype=object) + + for ref_axes in second_ref_axes: + i, j = flatten([rst_axis] * n for rst_axis, n in ref_axes) + + ruv = make_obj_array( + [forward_metric_nth_derivative(actx, dcoll, xyz_axis, ref_axes, dd=dd) + for xyz_axis in range(dcoll.ambient_dim)] + ) + form2[i, j] = form2[j, i] = normal.dot(ruv) + + return form2 + + +def shape_operator(actx: ArrayContext, dcoll: DiscretizationCollection, + dd=None) -> np.ndarray: + r"""Computes the shape operator (also called the curvature tensor) containing + second order derivatives: + + .. math:: + + C(x) = \begin{bmatrix} + \partial_{uu} x & \partial_{uv} x \\ + \partial_{uv} x & \partial_{vv} x + \end{bmatrix} + + where :math:`x(u, v)` defines a parameterized surface, and :math:`u,v` are + coordinates on the parameterized surface. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + :returns: a rank-2 object array describing the shape operator. + """ + + inv_form1 = inverse_first_fundamental_form(actx, dcoll, dd=dd) + form2 = second_fundamental_form(actx, dcoll, dd=dd) + + return -form2.dot(inv_form1) + + +def summed_curvature(actx: ArrayContext, dcoll: DiscretizationCollection, + dd=None) -> DOFArray: + r"""Computes the sum of the principal curvatures: + + .. math:: + + \kappa = \operatorname{Trace}(C(x)) + + where :math:`x(u, v)` defines a parameterized surface, :math:`u,v` are + coordinates on the parameterized surface, and :math:`C(x)` is the shape + operator. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + :returns: a :class:`~meshmode.dof_array.DOFArray` containing the summed + curvature at each nodal coordinate. + """ + + if dd is None: + dd = DD_VOLUME + + dim = dcoll.ambient_dim - 1 + + if dcoll.ambient_dim == 1: + return 0.0 + + if dcoll.ambient_dim == dim: + return 0.0 + + return np.trace(shape_operator(actx, dcoll, dd=dd)) + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/models/__init__.py b/grudge/models/__init__.py index 8486bd2e059f0026081cc88a90bb258a5a08f9d5..bc780365cd89169aae11358288b17d57825c61cd 100644 --- a/grudge/models/__init__.py +++ b/grudge/models/__init__.py @@ -31,18 +31,6 @@ class Operator: documentation, to group related classes together in an inheritance tree. """ - pass - - -class TimeDependentOperator(Operator): - """A base class for time-dependent Discontinuous Galerkin operators. - - You may derive your own operators from this class, but, at present - this class provides no functionality. Its function is merely as - documentation, to group related classes together in an inheritance - tree. - """ - pass class HyperbolicOperator(Operator): diff --git a/grudge/models/advection.py b/grudge/models/advection.py index 980425e5be60079d46bd2067936cdad83f164ec7..37ed429be37250f102812127c5eab28a7a495b91 100644 --- a/grudge/models/advection.py +++ b/grudge/models/advection.py @@ -1,6 +1,9 @@ """Operators modeling advective phenomena.""" -__copyright__ = "Copyright (C) 2009-2017 Andreas Kloeckner, Bogdan Enache" +__copyright__ = """ +Copyright (C) 2009-2017 Andreas Kloeckner, Bogdan Enache +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,30 +27,33 @@ THE SOFTWARE. import numpy as np -import numpy.linalg as la +import grudge.op as op +import types + +from arraycontext.container.traversal import thaw from grudge.models import HyperbolicOperator -from grudge import sym # {{{ fluxes -def advection_weak_flux(flux_type, u, velocity): - normal = sym.normal(u.dd, len(velocity)) - v_dot_n = sym.cse(velocity.dot(normal), "v_dot_normal") +def advection_weak_flux(dcoll, flux_type, u_tpair, velocity): + r"""Compute the numerical flux for the advection operator + $(v \cdot \nabla)u$. + """ + actx = u_tpair.int.array_context + dd = u_tpair.dd + normal = thaw(op.normal(dcoll, dd), actx) + v_dot_n = np.dot(velocity, normal) flux_type = flux_type.lower() if flux_type == "central": - return u.avg * v_dot_n + return u_tpair.avg * v_dot_n elif flux_type == "lf": - norm_v = sym.sqrt((velocity**2).sum()) - return u.avg * v_dot_n + 0.5 * norm_v * (u.int - u.ext) + norm_v = np.sqrt(sum(velocity**2)) + return u_tpair.avg * v_dot_n + 0.5 * norm_v * (u_tpair.int - u_tpair.ext) elif flux_type == "upwind": - u_upwind = sym.If( - sym.Comparison(v_dot_n, ">", 0), - u.int, # outflow - u.ext # inflow - ) + u_upwind = actx.np.where(v_dot_n > 0, u_tpair.int, u_tpair.ext) return u_upwind * v_dot_n else: raise ValueError(f"flux '{flux_type}' is not implemented") @@ -58,210 +64,312 @@ def advection_weak_flux(flux_type, u, velocity): # {{{ constant-coefficient advection class AdvectionOperatorBase(HyperbolicOperator): - flux_types = [ - "central", - "upwind", - "lf" - ] - - def __init__(self, v, inflow_u, flux_type="central"): - self.ambient_dim = len(v) - self.v = v - self.inflow_u = inflow_u - self.flux_type = flux_type + flux_types = ["central", "upwind", "lf"] + def __init__(self, dcoll, v, inflow_u=None, flux_type="central"): if flux_type not in self.flux_types: raise ValueError(f"unknown flux type: '{flux_type}'") - def weak_flux(self, u): - return advection_weak_flux(self.flux_type, u, self.v) + if inflow_u is not None: + if not isinstance(inflow_u, types.LambdaType): + raise ValueError( + "A specified inflow_u must be a lambda function of time `t`" + ) + + self.dcoll = dcoll + self.v = v + self.inflow_u = inflow_u + self.flux_type = flux_type + + def weak_flux(self, u_tpair): + return advection_weak_flux(self.dcoll, self.flux_type, u_tpair, self.v) def max_eigenvalue(self, t=None, fields=None, discr=None): - return la.norm(self.v) + return np.linalg.norm(self.v) class StrongAdvectionOperator(AdvectionOperatorBase): - def flux(self, u): - normal = sym.normal(u.dd, self.ambient_dim) - v_dot_normal = sym.cse(self.v.dot(normal), "v_dot_normal") + def flux(self, u_tpair): + actx = u_tpair.int.array_context + dd = u_tpair.dd + normal = thaw(op.normal(self.dcoll, dd), actx) + v_dot_normal = np.dot(self.v, normal) - return u.int * v_dot_normal - self.weak_flux(u) + return u_tpair.int * v_dot_normal - self.weak_flux(u_tpair) - def sym_operator(self): + def operator(self, t, u): from meshmode.mesh import BTAG_ALL - u = sym.var("u") + dcoll = self.dcoll - def flux(pair): - return sym.project(pair.dd, "all_faces")( - self.flux(pair)) + def flux(tpair): + return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) - return ( - - self.v.dot(sym.nabla(self.ambient_dim)*u) - + sym.InverseMassOperator()( - sym.FaceMassOperator()( - flux(sym.int_tpair(u)) - + flux(sym.bv_tpair(BTAG_ALL, u, self.inflow_u)) + if self.inflow_u is not None: + inflow_flux = flux(op.bv_trace_pair(dcoll, + BTAG_ALL, + interior=u, + exterior=self.inflow_u(t))) + else: + inflow_flux = 0 - # FIXME: Add back support for inflow/outflow tags - #+ flux(sym.bv_tpair(self.inflow_tag, u, bc_in)) - #+ flux(sym.bv_tpair(self.outflow_tag, u, bc_out)) - ))) + return ( + -self.v.dot(op.local_grad(dcoll, u)) + + op.inverse_mass( + dcoll, + op.face_mass( + dcoll, + sum(flux(tpair) for tpair in op.interior_trace_pairs(dcoll, u)) + + inflow_flux + + # FIXME: Add support for inflow/outflow tags + # + flux(op.bv_trace_pair(dcoll, + # self.inflow_tag, + # interior=u, + # exterior=bc_in)) + # + flux(op.bv_trace_pair(dcoll, + # self.outflow_tag, + # interior=u, + # exterior=bc_out)) + ) + ) + ) class WeakAdvectionOperator(AdvectionOperatorBase): - def flux(self, u): - return self.weak_flux(u) + def flux(self, u_tpair): + return self.weak_flux(u_tpair) - def sym_operator(self): + def operator(self, t, u): from meshmode.mesh import BTAG_ALL - u = sym.var("u") + dcoll = self.dcoll - def flux(pair): - return sym.project(pair.dd, "all_faces")( - self.flux(pair)) + def flux(tpair): + return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) - bc_in = self.inflow_u - # bc_out = sym.project(DD_VOLUME, self.outflow_tag)(u) + if self.inflow_u is not None: + inflow_flux = flux(op.bv_trace_pair(dcoll, + BTAG_ALL, + interior=u, + exterior=self.inflow_u(t))) + else: + inflow_flux = 0 - return sym.InverseMassOperator()( - np.dot( - self.v, sym.stiffness_t(self.ambient_dim)*u) - - sym.FaceMassOperator()( - flux(sym.int_tpair(u)) - + flux(sym.bv_tpair(BTAG_ALL, u, bc_in)) - - # FIXME: Add back support for inflow/outflow tags - #+ flux(sym.bv_tpair(self.inflow_tag, u, bc_in)) - #+ flux(sym.bv_tpair(self.outflow_tag, u, bc_out)) - )) + return ( + op.inverse_mass( + dcoll, + np.dot(self.v, op.weak_local_grad(dcoll, u)) + - op.face_mass( + dcoll, + sum(flux(tpair) for tpair in op.interior_trace_pairs(dcoll, u)) + + inflow_flux + + # FIXME: Add support for inflow/outflow tags + # + flux(op.bv_trace_pair(dcoll, + # self.inflow_tag, + # interior=u, + # exterior=bc_in)) + # + flux(op.bv_trace_pair(dcoll, + # self.outflow_tag, + # interior=u, + # exterior=bc_out)) + ) + ) + ) # }}} +def to_quad_int_tpairs(dcoll, u, quad_tag): + from grudge.dof_desc import DISCR_TAG_QUAD + from grudge.trace_pair import TracePair + + if issubclass(quad_tag, DISCR_TAG_QUAD): + return [ + TracePair( + tpair.dd.with_discr_tag(quad_tag), + interior=op.project( + dcoll, tpair.dd, + tpair.dd.with_discr_tag(quad_tag), tpair.int + ), + exterior=op.project( + dcoll, tpair.dd, + tpair.dd.with_discr_tag(quad_tag), tpair.ext + ) + ) for tpair in op.interior_trace_pairs(dcoll, u) + ] + else: + return op.interior_trace_pairs(dcoll, u) + + # {{{ variable-coefficient advection class VariableCoefficientAdvectionOperator(AdvectionOperatorBase): - def __init__(self, v, inflow_u, flux_type="central", quad_tag="product"): - super().__init__( - v, inflow_u, flux_type=flux_type) + def __init__(self, dcoll, v, inflow_u, flux_type="central", quad_tag=None): + super().__init__(dcoll, v, inflow_u, flux_type=flux_type) + + if quad_tag is None: + from grudge.dof_desc import DISCR_TAG_BASE + quad_tag = DISCR_TAG_BASE self.quad_tag = quad_tag - def flux(self, u): + def flux(self, u_tpair): from grudge.dof_desc import DD_VOLUME - surf_v = sym.project(DD_VOLUME, u.dd)(self.v) - return advection_weak_flux(self.flux_type, u, surf_v) + surf_v = op.project(self.dcoll, DD_VOLUME, u_tpair.dd, self.v) + return advection_weak_flux(self.dcoll, self.flux_type, u_tpair, surf_v) - def sym_operator(self): + def operator(self, t, u): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.mesh import BTAG_ALL from meshmode.discretization.connection import FACE_RESTR_ALL - u = sym.var("u") - - def flux(pair): - return sym.project(pair.dd, face_dd)(self.flux(pair)) - face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) boundary_dd = DOFDesc(BTAG_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) - to_quad = sym.project(DD_VOLUME, quad_dd) - stiff_t_op = sym.stiffness_t(self.ambient_dim, - dd_in=quad_dd, dd_out=DD_VOLUME) + dcoll = self.dcoll + + def flux(tpair): + return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair)) + + def to_quad(arg): + return op.project(dcoll, DD_VOLUME, quad_dd, arg) + + if self.inflow_u is not None: + inflow_flux = flux(op.bv_trace_pair(dcoll, + boundary_dd, + interior=u, + exterior=self.inflow_u(t))) + else: + inflow_flux = 0 quad_v = to_quad(self.v) quad_u = to_quad(u) - return sym.InverseMassOperator()( - sum(stiff_t_op[n](quad_u * quad_v[n]) - for n in range(self.ambient_dim)) - - sym.FaceMassOperator(face_dd, DD_VOLUME)( - flux(sym.int_tpair(u, self.quad_tag)) - + flux(sym.bv_tpair(boundary_dd, u, self.inflow_u)) - - # FIXME: Add back support for inflow/outflow tags - #+ flux(sym.bv_tpair(self.inflow_tag, u, bc_in)) - #+ flux(sym.bv_tpair(self.outflow_tag, u, bc_out)) - )) + return ( + op.inverse_mass( + dcoll, + sum(op.weak_local_d_dx(dcoll, quad_dd, d, quad_u * quad_v[d]) + for d in range(dcoll.ambient_dim)) + - op.face_mass( + dcoll, + face_dd, + sum(flux(quad_tpair) + for quad_tpair in to_quad_int_tpairs(dcoll, u, + self.quad_tag)) + + inflow_flux + + # FIXME: Add support for inflow/outflow tags + # + flux(op.bv_trace_pair(dcoll, + # self.inflow_tag, + # interior=u, + # exterior=bc_in)) + # + flux(op.bv_trace_pair(dcoll, + # self.outflow_tag, + # interior=u, + # exterior=bc_out)) + ) + ) + ) + # }}} # {{{ closed surface advection -def v_dot_n_tpair(velocity, dd=None): - from grudge.dof_desc import DOFDesc +def v_dot_n_tpair(actx, dcoll, velocity, trace_dd): + from grudge.dof_desc import DTAG_BOUNDARY + from grudge.trace_pair import TracePair from meshmode.discretization.connection import FACE_RESTR_INTERIOR - if dd is None: - dd = DOFDesc(FACE_RESTR_INTERIOR) + normal = thaw(op.normal(dcoll, trace_dd.with_discr_tag(None)), actx) + v_dot_n = velocity.dot(normal) + i = op.project(dcoll, trace_dd.with_discr_tag(None), trace_dd, v_dot_n) - ambient_dim = len(velocity) - normal = sym.normal(dd.with_discr_tag(None), - ambient_dim, dim=ambient_dim - 2) + if trace_dd.domain_tag is FACE_RESTR_INTERIOR: + e = dcoll.opposite_face_connection()(i) + elif isinstance(trace_dd.domain_tag, DTAG_BOUNDARY): + e = dcoll.distributed_boundary_swap_connection(trace_dd)(i) + else: + raise ValueError("Unrecognized domain tag: %s" % trace_dd.domain_tag) - return sym.int_tpair(velocity.dot(normal), - qtag=dd.discretization_tag, - from_dd=dd.with_discr_tag(None)) + return TracePair(trace_dd, interior=i, exterior=e) -def surface_advection_weak_flux(flux_type, u, velocity): - v_dot_n = v_dot_n_tpair(velocity, dd=u.dd) +def surface_advection_weak_flux(dcoll, flux_type, u_tpair, velocity): + actx = u_tpair.int.array_context + v_dot_n = v_dot_n_tpair(actx, dcoll, velocity, trace_dd=u_tpair.dd) # NOTE: the normals in v_dot_n point to the exterior of their respective # elements, so this is actually just an average - v_dot_n = sym.cse(0.5 * (v_dot_n.int - v_dot_n.ext), "v_dot_normal") + v_dot_n = 0.5 * (v_dot_n.int - v_dot_n.ext) flux_type = flux_type.lower() if flux_type == "central": - return u.avg * v_dot_n + return u_tpair.avg * v_dot_n elif flux_type == "lf": - return u.avg * v_dot_n + 0.5 * sym.fabs(v_dot_n) * (u.int - u.ext) + return (u_tpair.avg * v_dot_n + + 0.5 * actx.np.fabs(v_dot_n) * (u_tpair.int - u_tpair.ext)) else: raise ValueError(f"flux '{flux_type}' is not implemented") class SurfaceAdvectionOperator(AdvectionOperatorBase): - def __init__(self, v, flux_type="central", quad_tag=None): - super().__init__( - v, inflow_u=None, flux_type=flux_type) + def __init__(self, dcoll, v, flux_type="central", quad_tag=None): + super().__init__(dcoll, v, inflow_u=None, flux_type=flux_type) + + if quad_tag is None: + from grudge.dof_desc import DISCR_TAG_BASE + quad_tag = DISCR_TAG_BASE + self.quad_tag = quad_tag - def flux(self, u): + def flux(self, u_tpair): from grudge.dof_desc import DD_VOLUME - surf_v = sym.project(DD_VOLUME, u.dd.with_discr_tag(None))(self.v) - return surface_advection_weak_flux(self.flux_type, u, surf_v) + surf_v = op.project(self.dcoll, DD_VOLUME, + u_tpair.dd.with_discr_tag(None), self.v) + return surface_advection_weak_flux(self.dcoll, + self.flux_type, + u_tpair, + surf_v) - def sym_operator(self): + def operator(self, t, u): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.discretization.connection import FACE_RESTR_ALL - u = sym.var("u") - - def flux(pair): - return sym.project(pair.dd, face_dd)(self.flux(pair)) - face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) - to_quad = sym.project(DD_VOLUME, quad_dd) - stiff_t_op = sym.stiffness_t(self.ambient_dim, - dd_in=quad_dd, dd_out=DD_VOLUME) + dcoll = self.dcoll + + def flux(tpair): + return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair)) + + def to_quad(arg): + return op.project(dcoll, DD_VOLUME, quad_dd, arg) quad_v = to_quad(self.v) quad_u = to_quad(u) - return sym.InverseMassOperator()( - sum(stiff_t_op[n](quad_u * quad_v[n]) - for n in range(self.ambient_dim)) - - sym.FaceMassOperator(face_dd, DD_VOLUME)( - flux(sym.int_tpair(u, self.quad_tag)) - ) + return ( + op.inverse_mass( + dcoll, + sum(op.weak_local_d_dx(dcoll, quad_dd, d, quad_u * quad_v[d]) + for d in range(dcoll.ambient_dim)) + - op.face_mass( + dcoll, + face_dd, + sum(flux(quad_tpair) + for quad_tpair in to_quad_int_tpairs(dcoll, u, + self.quad_tag)) ) + ) + ) # }}} + # vim: foldmethod=marker diff --git a/grudge/models/burgers.py b/grudge/models/burgers.py deleted file mode 100644 index 0be0b6ffffe5027fd360d21fa999cceb99ca64ed..0000000000000000000000000000000000000000 --- a/grudge/models/burgers.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Burgers operator.""" - -__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -from grudge.models import HyperbolicOperator -import numpy -from grudge.second_order import CentralSecondDerivative - - -class BurgersOperator(HyperbolicOperator): - def __init__(self, dimensions, viscosity=None, - viscosity_scheme=CentralSecondDerivative()): - # yes, you read that right--no BCs, 1D only. - # (well--you can run the operator on a 2D grid. If you must.) - self.dimensions = dimensions - self.viscosity = viscosity - self.viscosity_scheme = viscosity_scheme - - def characteristic_velocity_optemplate(self, field): - from grudge.symbolic.operators import ( - ElementwiseMaxOperator) - return ElementwiseMaxOperator()(field**2)**0.5 - - def bind_characteristic_velocity(self, discr): - from grudge.symbolic import Field - compiled = discr.compile( - self.characteristic_velocity_optemplate( - Field("u"))) - - def do(u): - return compiled(u=u) - - return do - - def sym_operator(self, with_sensor): - from grudge.symbolic import ( - Field, - make_stiffness_t, - make_nabla, - InverseMassOperator, - ElementwiseMaxOperator, - get_flux_operator) - - from grudge.symbolic.operators import ( - QuadratureGridUpsampler, - QuadratureInteriorFacesGridUpsampler) - - to_quad = QuadratureGridUpsampler("quad") - to_if_quad = QuadratureInteriorFacesGridUpsampler("quad") - - u = Field("u") - u0 = Field("u0") - - # boundary conditions ------------------------------------------------- - minv_st = make_stiffness_t(self.dimensions) - nabla = make_nabla(self.dimensions) - m_inv = InverseMassOperator() - - def flux(u): - return u**2/2 - #return u0*u - - emax_u = self.characteristic_velocity_optemplate(u) - from grudge.flux.tools import make_lax_friedrichs_flux - from pytools.obj_array import make_obj_array - num_flux = make_lax_friedrichs_flux( - #u0, - to_if_quad(emax_u), - make_obj_array([to_if_quad(u)]), - [make_obj_array([flux(to_if_quad(u))])], - [], strong=False)[0] - - from grudge.second_order import SecondDerivativeTarget - - if self.viscosity is not None or with_sensor: - viscosity_coeff = 0 - if with_sensor: - viscosity_coeff += Field("sensor") - - if isinstance(self.viscosity, float): - viscosity_coeff += self.viscosity - elif self.viscosity is None: - pass - else: - raise TypeError("unsupported type of viscosity coefficient") - - # strong_form here allows IPDG to reuse the value of grad u. - grad_tgt = SecondDerivativeTarget( - self.dimensions, strong_form=True, - operand=u) - - self.viscosity_scheme.grad(grad_tgt, bc_getter=None, - dirichlet_tags=[], neumann_tags=[]) - - div_tgt = SecondDerivativeTarget( - self.dimensions, strong_form=False, - operand=viscosity_coeff*grad_tgt.minv_all) - - self.viscosity_scheme.div(div_tgt, - bc_getter=None, - dirichlet_tags=[], neumann_tags=[]) - - viscosity_bit = div_tgt.minv_all - else: - viscosity_bit = 0 - - return m_inv((minv_st[0](flux(to_quad(u)))) - num_flux) \ - + viscosity_bit - - def bind(self, discr, u0=1, sensor=None): - compiled_sym_operator = discr.compile( - self.sym_operator(with_sensor=sensor is not None)) - - from grudge.mesh import check_bc_coverage - check_bc_coverage(discr.mesh, []) - - def rhs(t, u): - kwargs = {} - if sensor is not None: - kwargs["sensor"] = sensor(u) - return compiled_sym_operator(u=u, u0=u0, **kwargs) - - return rhs - - def max_eigenvalue(self, t=None, fields=None, discr=None): - return discr.nodewise_max(fields) diff --git a/grudge/models/diffusion.py b/grudge/models/diffusion.py deleted file mode 100644 index 4c2db682f92ce985121e140568732e166ca4c9e3..0000000000000000000000000000000000000000 --- a/grudge/models/diffusion.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Operators modeling diffusive phenomena.""" - -__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -import numpy - -import grudge.data -from grudge.models import TimeDependentOperator -from grudge.models.poisson import LaplacianOperatorBase -from grudge.second_order import CentralSecondDerivative - - -class DiffusionOperator(TimeDependentOperator, LaplacianOperatorBase): - def __init__(self, dimensions, diffusion_tensor=None, - dirichlet_bc=grudge.data.make_tdep_constant(0), dirichlet_tag="dirichlet", - neumann_bc=grudge.data.make_tdep_constant(0), neumann_tag="neumann", - scheme=CentralSecondDerivative()): - self.dimensions = dimensions - - self.scheme = scheme - - self.dirichlet_bc = dirichlet_bc - self.dirichlet_tag = dirichlet_tag - self.neumann_bc = neumann_bc - self.neumann_tag = neumann_tag - - if diffusion_tensor is None: - diffusion_tensor = numpy.eye(dimensions) - self.diffusion_tensor = diffusion_tensor - - def bind(self, discr): - """Return a :class:`BoundPoissonOperator`.""" - - assert self.dimensions == discr.dimensions - - from grudge.mesh import check_bc_coverage - check_bc_coverage(discr.mesh, [self.dirichlet_tag, self.neumann_tag]) - - return BoundDiffusionOperator(self, discr) - - def estimate_timestep(self, discr, - stepper=None, stepper_class=None, stepper_args=None, - t=None, fields=None): - """Estimate the largest stable timestep, given a time stepper - `stepper_class`. If none is given, RK4 is assumed. - """ - - rk4_dt = 0.2 \ - * (discr.dt_non_geometric_factor() - * discr.dt_geometric_factor())**2 - - from grudge.timestep.stability import \ - approximate_rk4_relative_imag_stability_region - return rk4_dt * approximate_rk4_relative_imag_stability_region( - stepper, stepper_class, stepper_args) - - - - -class BoundDiffusionOperator(grudge.iterative.OperatorBase): - """Returned by :meth:`DiffusionOperator.bind`.""" - - def __init__(self, diffusion_op, discr): - grudge.iterative.OperatorBase.__init__(self) - self.discr = discr - - dop = self.diffusion_op = diffusion_op - - op = dop.sym_operator(apply_minv=True) - - self.compiled_op = discr.compile(op) - - # Check whether use of Poincaré mean-value method is required. - # (for pure Neumann or pure periodic) - - from grudge.mesh import BTAG_ALL - self.poincare_mean_value_hack = ( - len(self.discr.get_boundary(BTAG_ALL).nodes) - == len(self.discr.get_boundary(diffusion_op.neumann_tag).nodes)) - - def __call__(self, t, u): - dop = self.diffusion_op - - context = {"u": u} - - if not isinstance(self.diffusion_op.diffusion_tensor, numpy.ndarray): - self.diffusion = dop.diffusion_tensor.volume_interpolant(t, self.discr) - self.neu_diff = dop.diffusion_tensor.boundary_interpolant( - t, self.discr, dop.neumann_tag) - - context["diffusion"] = self.diffusion - context["neumann_diffusion"] = self.neu_diff - - if not self.discr.get_boundary(dop.dirichlet_tag).is_empty(): - context["dir_bc"] = dop.dirichlet_bc.boundary_interpolant( - t, self.discr, dop.dirichlet_tag) - if not self.discr.get_boundary(dop.neumann_tag).is_empty(): - context["neu_bc"] = dop.neumann_bc.boundary_interpolant( - t, self.discr, dop.neumann_tag) - - return self.compiled_op(**context) diff --git a/grudge/models/em.py b/grudge/models/em.py index 3f4bc13d42a017fcee36ba7b05237c512ca11451..44ec4a6edeccb7abc2d0a3bfe14f0673f8db8e14 100644 --- a/grudge/models/em.py +++ b/grudge/models/em.py @@ -4,6 +4,7 @@ __copyright__ = """ Copyright (C) 2007-2017 Andreas Kloeckner Copyright (C) 2010 David Powell Copyright (C) 2017 Bogdan Enache +Copyright (C) 2021 University of Illinois Board of Trustees """ __license__ = """ @@ -26,13 +27,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from pytools import memoize_method + +from arraycontext.container.traversal import thaw from grudge.models import HyperbolicOperator + from meshmode.mesh import BTAG_ALL, BTAG_NONE -from grudge import sym + +from pytools import memoize_method from pytools.obj_array import flat_obj_array, make_obj_array +import grudge.op as op + class MaxwellOperator(HyperbolicOperator): """A strong-form 3D Maxwell operator which supports fixed or variable @@ -43,7 +49,7 @@ class MaxwellOperator(HyperbolicOperator): _default_dimensions = 3 - def __init__(self, epsilon, mu, + def __init__(self, dcoll, epsilon, mu, flux_type, bdry_flux_type=None, pec_tag=BTAG_ALL, @@ -66,6 +72,7 @@ class MaxwellOperator(HyperbolicOperator): boundary condition """ + self.dcoll = dcoll self.dimensions = dimensions or self._default_dimensions space_subset = [True]*self.dimensions + [False]*(3-self.dimensions) @@ -103,7 +110,7 @@ class MaxwellOperator(HyperbolicOperator): self.current = current self.incident_bc_data = incident_bc - def flux(self, w): + def flux(self, wtpair): """The numerical flux for variable coefficients. :param flux_type: can be in [0,1] for anything between central and upwind, @@ -112,10 +119,10 @@ class MaxwellOperator(HyperbolicOperator): As per Hesthaven and Warburton page 433. """ - normal = sym.normal(w.dd, self.dimensions) + normal = thaw(op.normal(self.dcoll, wtpair.dd), self.dcoll._setup_actx) if self.fixed_material: - e, h = self.split_eh(w) + e, h = self.split_eh(wtpair) epsilon = self.epsilon mu = self.mu @@ -168,13 +175,18 @@ class MaxwellOperator(HyperbolicOperator): e, h = self.split_eh(w) - nabla = sym.nabla(self.dimensions) + # Object array of derivative operators + nabla = flat_obj_array( + [_Dx(self.dcoll, i) for i in range(self.dimensions)] + ) def e_curl(field): - return self.space_cross_e(nabla, field) + return self.space_cross_e(nabla, field, + three_mult=lambda lc, x, y: lc * (x * y)) def h_curl(field): - return self.space_cross_h(nabla, field) + return self.space_cross_h(nabla, field, + three_mult=lambda lc, x, y: lc * (x * y)) # in conservation form: u_t + A u_x = 0 return flat_obj_array( @@ -187,8 +199,8 @@ class MaxwellOperator(HyperbolicOperator): """ e, h = self.split_eh(w) - pec_e = sym.cse(sym.project("vol", self.pec_tag)(e)) - pec_h = sym.cse(sym.project("vol", self.pec_tag)(h)) + pec_e = op.project(self.dcoll, "vol", self.pec_tag, e) + pec_h = op.project(self.dcoll, "vol", self.pec_tag, h) return flat_obj_array(-pec_e, pec_h) @@ -197,8 +209,8 @@ class MaxwellOperator(HyperbolicOperator): """ e, h = self.split_eh(w) - pmc_e = sym.cse(sym.project("vol", self.pmc_tag)(e)) - pmc_h = sym.cse(sym.project("vol", self.pmc_tag)(h)) + pmc_e = op.project(self.dcoll, "vol", self.pmc_tag, e) + pmc_h = op.project(self.dcoll, "vol", self.pmc_tag, h) return flat_obj_array(pmc_e, -pmc_h) @@ -207,7 +219,8 @@ class MaxwellOperator(HyperbolicOperator): absorbing boundary conditions. """ - absorb_normal = sym.normal(self.absorb_tag, self.dimensions) + absorb_normal = thaw(op.normal(self.dcoll, dd=self.absorb_tag), + self.dcoll._setup_actx) e, h = self.split_eh(w) @@ -218,8 +231,8 @@ class MaxwellOperator(HyperbolicOperator): absorb_Z = (mu/epsilon)**0.5 # noqa: N806 absorb_Y = 1/absorb_Z # noqa: N806 - absorb_e = sym.cse(sym.project("vol", self.absorb_tag)(e)) - absorb_h = sym.cse(sym.project("vol", self.absorb_tag)(h)) + absorb_e = op.project(self.dcoll, "vol", self.absorb_tag, e) + absorb_h = op.project(self.dcoll, "vol", self.absorb_tag, h) bc = flat_obj_array( absorb_e + 1/2*(self.space_cross_h(absorb_normal, self.space_cross_e( @@ -247,9 +260,9 @@ class MaxwellOperator(HyperbolicOperator): if is_zero(incident_bc_data): return make_obj_array([0]*fld_cnt) else: - return sym.cse(-incident_bc_data) + return -incident_bc_data - def sym_operator(self, w=None): + def operator(self, t, w): """The full operator template - the high level description of the Maxwell operator. @@ -257,7 +270,6 @@ class MaxwellOperator(HyperbolicOperator): derivatives, flux, boundary conditions etc. """ from grudge.tools import count_subset - w = sym.make_sym_array("w", count_subset(self.get_eh_subset())) elec_components = count_subset(self.get_eh_subset()[0:3]) mag_components = count_subset(self.get_eh_subset()[3:6]) @@ -274,17 +286,23 @@ class MaxwellOperator(HyperbolicOperator): (self.incident_tag, self.incident_bc(w)), ] + dcoll = self.dcoll + def flux(pair): - return sym.project(pair.dd, "all_faces")(self.flux(pair)) + return op.project(dcoll, pair.dd, "all_faces", self.flux(pair)) return ( - - self.local_derivatives(w) - - sym.InverseMassOperator()(sym.FaceMassOperator()( - flux(sym.int_tpair(w)) - + sum( - flux(sym.bv_tpair(tag, w, bc)) - for tag, bc in tags_and_bcs) - ))) / material_divisor + - self.local_derivatives(w) + - op.inverse_mass( + dcoll, + op.face_mass( + dcoll, + sum(flux(tpair) for tpair in op.interior_trace_pairs(dcoll, w)) + + sum(flux(op.bv_trace_pair(dcoll, tag, w, bc)) + for tag, bc in tags_and_bcs) + ) + ) + ) / material_divisor @memoize_method def partial_to_eh_subsets(self): @@ -322,10 +340,9 @@ class MaxwellOperator(HyperbolicOperator): if self.fixed_material: return 1/sqrt(self.epsilon*self.mu) # a number else: - import grudge.symbolic as sym - return sym.NodalMax("vol")( - 1 / sym.sqrt(self.epsilon * self.mu) - ) + actx = self.dcoll._setup_actx + return op.nodal_max(self.dcoll, "vol", + 1 / actx.np.sqrt(self.epsilon * self.mu)) def max_eigenvalue(self, t, fields=None, discr=None, context=None): if context is None: @@ -345,6 +362,17 @@ class MaxwellOperator(HyperbolicOperator): self.incident_tag]) +# NOTE: Hack for getting the derivative operators to play nice +# with grudge.tools.SubsettableCrossProduct +class _Dx: + def __init__(self, dcoll, i): + self.dcoll = dcoll + self.i = i + + def __mul__(self, other): + return op.local_d_dx(self.dcoll, self.i, other) + + class TMMaxwellOperator(MaxwellOperator): """A 2D TM Maxwell operator with PEC boundaries. @@ -405,7 +433,7 @@ class SourceFree1DMaxwellOperator(MaxwellOperator): ) -def get_rectangular_cavity_mode(E_0, mode_indices): # noqa: N803 +def get_rectangular_cavity_mode(actx, nodes, t, E_0, mode_indices): # noqa: N803 """A rectangular TM cavity mode for a rectangle / cube with one corner at the origin and the other at (1,1[,1]).""" dims = len(mode_indices) @@ -421,43 +449,44 @@ def get_rectangular_cavity_mode(E_0, mode_indices): # noqa: N803 omega = numpy.sqrt(sum(f**2 for f in factors)) - nodes = sym.nodes(dims) x = nodes[0] y = nodes[1] if dims == 3: z = nodes[2] - sx = sym.sin(kx*x) - cx = sym.cos(kx*x) - sy = sym.sin(ky*y) - cy = sym.cos(ky*y) + zeros = 0*x + sx = actx.np.sin(kx*x) + cx = actx.np.cos(kx*x) + sy = actx.np.sin(ky*y) + cy = actx.np.cos(ky*y) if dims == 3: - sz = sym.sin(kz*z) - cz = sym.cos(kz*z) + sz = actx.np.sin(kz*z) + cz = actx.np.cos(kz*z) if dims == 2: - tfac = sym.ScalarVariable("t") * omega + tfac = t * omega result = flat_obj_array( - 0, - 0, - sym.sin(kx * x) * sym.sin(ky * y) * sym.cos(tfac), # ez - -ky * sym.sin(kx * x) * sym.cos(ky * y) * sym.sin(tfac) / omega, # hx - kx * sym.cos(kx * x) * sym.sin(ky * y) * sym.sin(tfac) / omega, # hy - 0, - ) + zeros, + zeros, + actx.np.sin(kx * x) * actx.np.sin(ky * y) * actx.np.cos(tfac), # ez + (-ky * actx.np.sin(kx * x) * actx.np.cos(ky * y) + * actx.np.sin(tfac) / omega), # hx + (kx * actx.np.cos(kx * x) * actx.np.sin(ky * y) + * actx.np.sin(tfac) / omega), # hy + zeros, + ) else: - tdep = sym.exp(-1j * omega * sym.ScalarVariable("t")) + tdep = numpy.exp(-1j * omega * t) gamma_squared = ky**2 + kx**2 result = flat_obj_array( -kx * kz * E_0*cx*sy*sz*tdep / gamma_squared, # ex -ky * kz * E_0*sx*cy*sz*tdep / gamma_squared, # ey E_0 * sx*sy*cz*tdep, # ez - -1j * omega * ky*E_0*sx*cy*cz*tdep / gamma_squared, # hx 1j * omega * kx*E_0*cx*sy*cz*tdep / gamma_squared, - 0, - ) + zeros, + ) return result diff --git a/grudge/models/gas_dynamics/__init__.py b/grudge/models/gas_dynamics/__init__.py deleted file mode 100644 index 7562bae3148bdf6e4d3833a737b4939c52a595ff..0000000000000000000000000000000000000000 --- a/grudge/models/gas_dynamics/__init__.py +++ /dev/null @@ -1,914 +0,0 @@ -"""Operator for compressible Navier-Stokes and Euler equations.""" - -__copyright__ = "Copyright (C) 2007 Hendrik Riedmann, Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -import numpy -import grudge.tools -import grudge.mesh -import grudge.data -from grudge.models import TimeDependentOperator -from pytools import Record -from grudge.tools import is_zero -from grudge.second_order import ( - StabilizedCentralSecondDerivative, - CentralSecondDerivative, - IPDGSecondDerivative) -from grudge.symbolic.primitives import make_common_subexpression as cse -from pytools import memoize_method -from grudge.symbolic.tools import make_sym_vector -from pytools.obj_array import make_obj_array, join_fields - -AXES = ["x", "y", "z", "w"] - -from grudge.symbolic.operators import ( - QuadratureGridUpsampler, - QuadratureInteriorFacesGridUpsampler) - -to_vol_quad = QuadratureGridUpsampler("gasdyn_vol") - -# It is recommended (though not required) that these two -# remain the same so that they can be computed together -# by the CUDA backend - -to_int_face_quad = QuadratureInteriorFacesGridUpsampler("gasdyn_face") -to_bdry_quad = QuadratureGridUpsampler("gasdyn_face") - - - - -# {{{ equations of state -class EquationOfState: - def q_to_p(self, op, q): - raise NotImplementedError - - def p_to_e(self, p, rho, u): - raise NotImplementedError - -class GammaLawEOS(EquationOfState): - # FIXME Shouldn't gamma only occur in the equation of state? - # I.e. shouldn't all uses of gamma go through the EOS? - - def __init__(self, gamma): - self.gamma = gamma - - def q_to_p(self, op, q): - return (self.gamma-1)*(op.e(q)-0.5*numpy.dot(op.rho_u(q), op.u(q))) - - def p_to_e(self, p, rho, u): - return p / (self.gamma - 1) + rho / 2 * numpy.dot(u, u) - -class PolytropeEOS(GammaLawEOS): - # inverse is same as superclass - - def q_to_p(self, op, q): - return op.rho(q)**self.gamma - -# }}} - - - - - -class GasDynamicsOperator(TimeDependentOperator): - """An nD Navier-Stokes and Euler operator. - - see JSH, TW: Nodal Discontinuous Galerkin Methods p.320 and p.206 - - dq/dt = d/dx * (-F + tau_:1) + d/dy * (-G + tau_:2) - - where e.g. in 2D - - q = (rho, rho_u_x, rho_u_y, E) - F = (rho_u_x, rho_u_x^2 + p, rho_u_x * rho_u_y / rho, u_x * (E + p)) - G = (rho_u_y, rho_u_x * rho_u_y / rho, rho_u_y^2 + p, u_y * (E + p)) - - tau_11 = mu * (2 * du/dx - 2/3 * (du/dx + dv/dy)) - tau_12 = mu * (du/dy + dv/dx) - tau_21 = tau_12 - tau_22 = mu * (2 * dv/dy - 2/3 * (du/dx + dv/dy)) - tau_31 = u * tau_11 + v * tau_12 - tau_32 = u * tau_21 + v * tau_22 - - For the heat flux: - - q = -k * nabla * T - k = c_p * mu / Pr - - Field order is [rho E rho_u_x rho_u_y ...]. - """ - - # {{{ initialization ------------------------------------------------------ - def __init__(self, dimensions, - gamma=None, mu=0, - bc_inflow=None, - bc_outflow=None, - bc_noslip=None, - bc_supersonic_inflow=None, - prandtl=None, spec_gas_const=1.0, - equation_of_state=None, - inflow_tag="inflow", - outflow_tag="outflow", - noslip_tag="noslip", - wall_tag="wall", - supersonic_inflow_tag="supersonic_inflow", - supersonic_outflow_tag="supersonic_outflow", - source=None, - second_order_scheme=CentralSecondDerivative(), - artificial_viscosity_mode=None, - ): - """ - :param source: should implement - :class:`grudge.data.IFieldDependentGivenFunction` - or be None. - - :param artificial_viscosity_mode: - """ - from grudge.data import ( - TimeConstantGivenFunction, - ConstantGivenFunction) - - if gamma is not None: - if equation_of_state is not None: - raise ValueError("can only specify one of gamma and equation_of_state") - - from warnings import warn - warn("argument gamma is deprecated in favor of equation_of_state", - DeprecationWarning, stacklevel=2) - - equation_of_state = GammaLawEOS(gamma) - - dull_bc = TimeConstantGivenFunction( - ConstantGivenFunction(make_obj_array( - [1, 1] + [0]*dimensions))) - if bc_inflow is None: - bc_inflow = dull_bc - if bc_outflow is None: - bc_outflow = dull_bc - if bc_noslip is None: - bc_noslip = dull_bc - if bc_supersonic_inflow is None: - bc_supersonic_inflow = dull_bc - - self.dimensions = dimensions - - self.prandtl = prandtl - self.spec_gas_const = spec_gas_const - self.mu = mu - - self.bc_inflow = bc_inflow - self.bc_outflow = bc_outflow - self.bc_noslip = bc_noslip - self.bc_supersonic_inflow = bc_supersonic_inflow - - self.inflow_tag = inflow_tag - self.outflow_tag = outflow_tag - self.noslip_tag = noslip_tag - self.wall_tag = wall_tag - self.supersonic_inflow_tag = supersonic_inflow_tag - self.supersonic_outflow_tag = supersonic_outflow_tag - - self.source = source - self.equation_of_state = equation_of_state - - self.second_order_scheme = second_order_scheme - - if artificial_viscosity_mode not in [ - "cns", "diffusion", "blended", None]: - raise ValueError("artificial_viscosity_mode has an invalid value") - - self.artificial_viscosity_mode = artificial_viscosity_mode - - - - # }}} - - # {{{ conversions --------------------------------------------------------- - def state(self): - return make_sym_vector("q", self.dimensions+2) - - @memoize_method - def volq_state(self): - return cse(to_vol_quad(self.state()), "vol_quad_state") - - @memoize_method - def faceq_state(self): - return cse(to_int_face_quad(self.state()), "face_quad_state") - - @memoize_method - def sensor(self): - from grudge.symbolic.primitives import Field - sensor = Field("sensor") - - def rho(self, q): - return q[0] - - def e(self, q): - return q[1] - - def rho_u(self, q): - return q[2:2+self.dimensions] - - def u(self, q): - return make_obj_array([ - rho_u_i/self.rho(q) - for rho_u_i in self.rho_u(q)]) - - def p(self,q): - return self.equation_of_state.q_to_p(self, q) - - def cse_u(self, q): - return cse(self.u(q), "u") - - def cse_rho(self, q): - return cse(self.rho(q), "rho") - - def cse_rho_u(self, q): - return cse(self.rho_u(q), "rho_u") - - def cse_p(self, q): - return cse(self.p(q), "p") - - def temperature(self, q): - c_v = 1 / (self.equation_of_state.gamma - 1) * self.spec_gas_const - return (self.e(q)/self.rho(q) - 0.5 * numpy.dot(self.u(q), self.u(q))) / c_v - - def cse_temperature(self, q): - return cse(self.temperature(q), "temperature") - - def get_mu(self, q, to_quad_op): - r""" - :param to_quad_op: If not *None*, represents an operator which transforms - nodal values onto a quadrature grid on which the returned :math:`\mu` - needs to be represented. In that case, *q* is assumed to already be on the - same quadrature grid. - """ - - if to_quad_op is None: - def to_quad_op(x): - return x - - if self.mu == "sutherland": - # Sutherland's law: !!!not tested!!! - t_s = 110.4 - mu_inf = 1.735e-5 - result = cse( - mu_inf * self.cse_temperature(q) ** 1.5 * (1 + t_s) - / (self.cse_temperature(q) + t_s), - "sutherland_mu") - else: - result = self.mu - - if self.artificial_viscosity_mode == "cns": - mapped_sensor = self.sensor() - else: - mapped_sensor = None - - if mapped_sensor is not None: - result = result + cse(to_quad_op(mapped_sensor), "quad_sensor") - - return cse(result, "mu") - - def primitive_to_conservative(self, prims, use_cses=True): - if not use_cses: - from grudge.symbolic.primitives import make_common_subexpression as cse - else: - def cse(x, name): return x - - rho = prims[0] - p = prims[1] - u = prims[2:] - e = self.equation_of_state.p_to_e(p, rho, u) - - return join_fields( - rho, - cse(e, "e"), - cse(rho * u, "rho_u")) - - def conservative_to_primitive(self, q, use_cses=True): - if use_cses: - from grudge.symbolic.primitives import make_common_subexpression as cse - else: - def cse(x, name): return x - - return join_fields( - self.rho(q), - self.p(q), - self.u(q)) - - def characteristic_velocity_optemplate(self, state): - from grudge.symbolic.operators import ElementwiseMaxOperator - - from grudge.symbolic.primitives import FunctionSymbol - sqrt = FunctionSymbol("sqrt") - - sound_speed = cse(sqrt( - self.equation_of_state.gamma*self.cse_p(state)/self.cse_rho(state)), - "sound_speed") - u = self.cse_u(state) - speed = cse(sqrt(numpy.dot(u, u)), "norm_u") + sound_speed - return ElementwiseMaxOperator()(speed) - - def bind_characteristic_velocity(self, discr): - state = make_sym_vector("q", self.dimensions+2) - - compiled = discr.compile( - self.characteristic_velocity_optemplate(state)) - - def do(q): - return compiled(q=q) - - return do - - # }}} - - # {{{ helpers for second-order part --------------------------------------- - - # {{{ compute gradient of state --------------------------------------- - def grad_of(self, var, faceq_var): - from grudge.second_order import SecondDerivativeTarget - grad_tgt = SecondDerivativeTarget( - self.dimensions, strong_form=False, - operand=var, - int_flux_operand=faceq_var, - bdry_flux_int_operand=faceq_var) - - self.second_order_scheme.grad(grad_tgt, - bc_getter=self.get_boundary_condition_for, - dirichlet_tags=self.get_boundary_tags(), - neumann_tags=[]) - - return grad_tgt.minv_all - - def grad_of_state(self): - dimensions = self.dimensions - - state = self.state() - - dq = numpy.zeros((len(state), dimensions), dtype=object) - - for i in range(len(state)): - dq[i,:] = self.grad_of( - state[i], self.faceq_state()[i]) - - return dq - - def grad_of_state_func(self, func, of_what_descr): - return cse(self.grad_of( - func(self.volq_state()), - func(self.faceq_state())), - "grad_"+of_what_descr) - - # }}} - - # {{{ viscous stress tensor - - def tau(self, to_quad_op, state, mu=None): - faceq_state = self.faceq_state() - - dimensions = self.dimensions - - # {{{ compute gradient of u --------------------------------------- - # Use the product rule to compute the gradient of - # u from the gradient of (rho u). This ensures we don't - # compute the derivatives twice. - - from pytools.obj_array import with_object_array_or_scalar - dq = with_object_array_or_scalar( - to_quad_op, self.grad_of_state()) - - q = cse(to_quad_op(state)) - - du = numpy.zeros((dimensions, dimensions), dtype=object) - for i in range(dimensions): - for j in range(dimensions): - du[i,j] = cse( - (dq[i+2,j] - self.cse_u(q)[i] * dq[0,j]) / self.rho(q), - "du%d_d%s" % (i, AXES[j])) - - # }}} - - # {{{ put together viscous stress tau ----------------------------- - from pytools import delta - - if mu is None: - mu = self.get_mu(q, to_quad_op) - - tau = numpy.zeros((dimensions, dimensions), dtype=object) - for i in range(dimensions): - for j in range(dimensions): - tau[i,j] = cse(mu * cse(du[i,j] + du[j,i] - - 2/self.dimensions * delta(i,j) * numpy.trace(du)), - "tau_%d%d" % (i, j)) - - return tau - - # }}} - - # }}} - - # }}} - - # {{{ heat conduction - - def heat_conduction_coefficient(self, to_quad_op): - mu = self.get_mu(self.state(), to_quad_op) - if self.prandtl is None or numpy.isinf(self.prandtl): - return 0 - - eos = self.equation_of_state - return (mu / self.prandtl) * (eos.gamma / (eos.gamma-1)) - - def heat_conduction_grad(self, to_quad_op): - grad_p_over_rho = self.grad_of_state_func( - lambda state: self.p(state)/self.rho(state), - "p_over_rho") - - return (self.heat_conduction_coefficient(to_quad_op) - * to_quad_op(grad_p_over_rho)) - - # }}} - - # {{{ flux - - def flux(self, q): - from pytools import delta - - return [ # one entry for each flux direction - cse(join_fields( - # flux rho - self.rho_u(q)[i], - - # flux E - cse(self.e(q)+self.cse_p(q))*self.cse_u(q)[i], - - # flux rho_u - make_obj_array([ - self.rho_u(q)[i]*self.cse_u(q)[j] - + delta(i,j) * self.cse_p(q) - for j in range(self.dimensions) - ]) - ), "%s_flux" % AXES[i]) - for i in range(self.dimensions)] - - # }}} - - # {{{ boundary conditions --------------------------------------------- - - def make_bc_info(self, bc_name, tag, state, state0=None): - """ - :param state0: The boundary 'free-stream' state around which the - BC is linearized. - """ - if state0 is None: - state0 = make_sym_vector(bc_name, self.dimensions+2) - - state0 = cse(to_bdry_quad(state0)) - - rho0 = self.rho(state0) - p0 = self.cse_p(state0) - u0 = self.cse_u(state0) - - c0 = (self.equation_of_state.gamma * p0 / rho0)**0.5 - - from grudge.symbolic import RestrictToBoundary - bdrize_op = RestrictToBoundary(tag) - - class SingleBCInfo(Record): - pass - - return SingleBCInfo( - rho0=rho0, p0=p0, u0=u0, c0=c0, - - # notation: suffix "m" for "minus", i.e. "interior" - drhom=cse(self.rho(cse(to_bdry_quad(bdrize_op(state)))) - - rho0, "drhom"), - dumvec=cse(self.cse_u(cse(to_bdry_quad(bdrize_op(state)))) - - u0, "dumvec"), - dpm=cse(self.cse_p(cse(to_bdry_quad(bdrize_op(state)))) - - p0, "dpm")) - - def outflow_state(self, state): - from grudge.symbolic import make_normal - normal = make_normal(self.outflow_tag, self.dimensions) - bc = self.make_bc_info("bc_q_out", self.outflow_tag, state) - - # see grudge/doc/maxima/euler.mac - return join_fields( - # bc rho - cse(bc.rho0 - + bc.drhom + numpy.dot(normal, bc.dumvec)*bc.rho0/(2*bc.c0) - - bc.dpm/(2*bc.c0*bc.c0), "bc_rho_outflow"), - - # bc p - cse(bc.p0 - + bc.c0*bc.rho0*numpy.dot(normal, bc.dumvec)/2 + bc.dpm/2, "bc_p_outflow"), - - # bc u - cse(bc.u0 - + bc.dumvec - normal*numpy.dot(normal, bc.dumvec)/2 - + bc.dpm*normal/(2*bc.c0*bc.rho0), "bc_u_outflow")) - - def inflow_state_inner(self, normal, bc, name): - # see grudge/doc/maxima/euler.mac - return join_fields( - # bc rho - cse(bc.rho0 - + numpy.dot(normal, bc.dumvec)*bc.rho0/(2*bc.c0) + bc.dpm/(2*bc.c0*bc.c0), "bc_rho_"+name), - - # bc p - cse(bc.p0 - + bc.c0*bc.rho0*numpy.dot(normal, bc.dumvec)/2 + bc.dpm/2, "bc_p_"+name), - - # bc u - cse(bc.u0 - + normal*numpy.dot(normal, bc.dumvec)/2 + bc.dpm*normal/(2*bc.c0*bc.rho0), "bc_u_"+name)) - - def inflow_state(self, state): - from grudge.symbolic import make_normal - normal = make_normal(self.inflow_tag, self.dimensions) - bc = self.make_bc_info("bc_q_in", self.inflow_tag, state) - return self.inflow_state_inner(normal, bc, "inflow") - - def noslip_state(self, state): - from grudge.symbolic import make_normal - state0 = join_fields( - make_sym_vector("bc_q_noslip", 2), - [0]*self.dimensions) - normal = make_normal(self.noslip_tag, self.dimensions) - bc = self.make_bc_info("bc_q_noslip", self.noslip_tag, state, state0) - return self.inflow_state_inner(normal, bc, "noslip") - - def wall_state(self, state): - from grudge.symbolic import RestrictToBoundary - bc = RestrictToBoundary(self.wall_tag)(state) - wall_rho = self.rho(bc) - wall_e = self.e(bc) # <3 eve - wall_rho_u = self.rho_u(bc) - - from grudge.symbolic import make_normal - normal = make_normal(self.wall_tag, self.dimensions) - - return join_fields( - wall_rho, - wall_e, - wall_rho_u - 2*numpy.dot(wall_rho_u, normal) * normal) - - @memoize_method - def get_primitive_boundary_conditions(self): - state = self.state() - - return { - self.outflow_tag: self.outflow_state(state), - self.inflow_tag: self.inflow_state(state), - self.noslip_tag: self.noslip_state(state) - } - - - @memoize_method - def get_conservative_boundary_conditions(self): - state = self.state() - - from grudge.symbolic import RestrictToBoundary - return { - self.supersonic_inflow_tag: - make_sym_vector("bc_q_supersonic_in", self.dimensions+2), - self.supersonic_outflow_tag: - RestrictToBoundary(self.supersonic_outflow_tag)( - state), - self.wall_tag: self.wall_state(state), - } - - @memoize_method - def get_boundary_tags(self): - return (set(self.get_primitive_boundary_conditions().keys()) - | set(self.get_conservative_boundary_conditions().keys())) - - @memoize_method - def _normalize_expr(self, expr): - """Normalize expressions for use as hash keys.""" - from grudge.symbolic.mappers import ( - QuadratureUpsamplerRemover, - CSERemover) - - return CSERemover()( - QuadratureUpsamplerRemover({}, do_warn=False)(expr)) - - @memoize_method - def _get_norm_primitive_exprs(self): - return [ - self._normalize_expr(expr) for expr in - self.conservative_to_primitive(self.state()) - ] - - @memoize_method - def get_boundary_condition_for(self, tag, expr): - prim_bcs = self.get_primitive_boundary_conditions() - cons_bcs = self.get_conservative_boundary_conditions() - - if tag in prim_bcs: - # BC is given in primitive variables, avoid converting - # to conservative and back. - try: - norm_expr = self._normalize_expr(expr) - prim_idx = self._get_norm_primitive_exprs().index(norm_expr) - except ValueError: - cbstate = self.primitive_to_conservative( - prim_bcs[tag]) - else: - return prim_bcs[tag][prim_idx] - else: - # BC is given in conservative variables, no potential - # for optimization. - - cbstate = to_bdry_quad(cons_bcs[tag]) - - # 'cbstate' is the boundary state in conservative variables. - - from grudge.symbolic.mappers import QuadratureUpsamplerRemover - expr = QuadratureUpsamplerRemover({}, do_warn=False)(expr) - - def subst_func(expr): - from pymbolic.primitives import Subscript, Variable - - if isinstance(expr, Subscript): - assert (isinstance(expr.aggregate, Variable) - and expr.aggregate.name == "q") - - return cbstate[expr.index] - elif isinstance(expr, Variable) and expr.name =="sensor": - from grudge.symbolic import RestrictToBoundary - result = RestrictToBoundary(tag)(self.sensor()) - return cse(to_bdry_quad(result), "bdry_sensor") - - from grudge.symbolic.mappers import SubstitutionMapper - return SubstitutionMapper(subst_func)(expr) - - # }}} - - # {{{ second order part - def div(self, vol_operand, int_face_operand): - from grudge.second_order import SecondDerivativeTarget - div_tgt = SecondDerivativeTarget( - self.dimensions, strong_form=False, - operand=vol_operand, - int_flux_operand=int_face_operand) - - self.second_order_scheme.div(div_tgt, - bc_getter=self.get_boundary_condition_for, - dirichlet_tags=list(self.get_boundary_tags()), - neumann_tags=[]) - - return div_tgt.minv_all - - def make_second_order_part(self): - state = self.state() - faceq_state = self.faceq_state() - volq_state = self.volq_state() - - volq_tau_mat = self.tau(to_vol_quad, state) - faceq_tau_mat = self.tau(to_int_face_quad, state) - - return join_fields( - 0, - self.div( - numpy.sum(volq_tau_mat*self.cse_u(volq_state), axis=1) - + self.heat_conduction_grad(to_vol_quad) - , - numpy.sum(faceq_tau_mat*self.cse_u(faceq_state), axis=1) - + self.heat_conduction_grad(to_int_face_quad) - , - ), - [ - self.div(volq_tau_mat[i], faceq_tau_mat[i]) - for i in range(self.dimensions)] - ) - - # }}} - - # {{{ operator template --------------------------------------------------- - def make_extra_terms(self): - return 0 - - def sym_operator(self, sensor_scaling=None, viscosity_only=False): - u = self.cse_u - rho = self.cse_rho - rho_u = self.rho_u - p = self.p - e = self.e - - # {{{ artificial diffusion - def make_artificial_diffusion(): - if self.artificial_viscosity_mode not in ["diffusion"]: - return 0 - - dq = self.grad_of_state() - - return make_obj_array([ - self.div( - to_vol_quad(self.sensor())*to_vol_quad(dq[i]), - to_int_face_quad(self.sensor())*to_int_face_quad(dq[i])) - for i in range(dq.shape[0])]) - # }}} - - # {{{ state setup - - volq_flux = self.flux(self.volq_state()) - faceq_flux = self.flux(self.faceq_state()) - - from grudge.symbolic.primitives import FunctionSymbol - sqrt = FunctionSymbol("sqrt") - - speed = self.characteristic_velocity_optemplate(self.state()) - - has_viscosity = not is_zero(self.get_mu(self.state(), to_quad_op=None)) - - # }}} - - # {{{ operator assembly ----------------------------------------------- - from grudge.flux.tools import make_lax_friedrichs_flux - from grudge.symbolic.operators import InverseMassOperator - - from grudge.symbolic.tools import make_stiffness_t - - primitive_bcs_as_quad_conservative = { - tag: self.primitive_to_conservative(to_bdry_quad(bc)) - for tag, bc in - self.get_primitive_boundary_conditions().items()} - - def get_bc_tuple(tag): - state = self.state() - bc = make_obj_array([ - self.get_boundary_condition_for(tag, s_i) for s_i in state]) - return tag, bc, self.flux(bc) - - first_order_part = InverseMassOperator()( - numpy.dot(make_stiffness_t(self.dimensions), volq_flux) - - make_lax_friedrichs_flux( - wave_speed=cse(to_int_face_quad(speed), "emax_c"), - - state=self.faceq_state(), fluxes=faceq_flux, - bdry_tags_states_and_fluxes=[ - get_bc_tuple(tag) for tag in self.get_boundary_tags()], - strong=False)) - - if viscosity_only: - first_order_part = 0*first_order_part - - result = join_fields( - first_order_part - + self.make_second_order_part() - + make_artificial_diffusion() - + self.make_extra_terms(), - speed) - - if self.source is not None: - result = result + join_fields( - make_sym_vector("source_vect", len(self.state())), - # extra field for speed - 0) - - return result - - # }}} - - # }}} - - # {{{ operator binding ---------------------------------------------------- - def bind(self, discr, sensor=None, sensor_scaling=None, viscosity_only=False): - if (sensor is None and - self.artificial_viscosity_mode is not None): - raise ValueError("must specify a sensor if using " - "artificial viscosity") - - bound_op = discr.compile(self.sym_operator( - sensor_scaling=sensor_scaling, - viscosity_only=False)) - - from grudge.mesh import check_bc_coverage - check_bc_coverage(discr.mesh, [ - self.inflow_tag, - self.outflow_tag, - self.noslip_tag, - self.wall_tag, - self.supersonic_inflow_tag, - self.supersonic_outflow_tag, - ]) - - if self.mu == 0 and not discr.get_boundary(self.noslip_tag).is_empty(): - raise RuntimeError("no-slip BCs only make sense for " - "viscous problems") - - def rhs(t, q): - extra_kwargs = {} - if self.source is not None: - extra_kwargs["source_vect"] = self.source.volume_interpolant( - t, q, discr) - - if sensor is not None: - extra_kwargs["sensor"] = sensor(q) - - opt_result = bound_op(q=q, - bc_q_in=self.bc_inflow.boundary_interpolant( - t, discr, self.inflow_tag), - bc_q_out=self.bc_outflow.boundary_interpolant( - t, discr, self.outflow_tag), - bc_q_noslip=self.bc_noslip.boundary_interpolant( - t, discr, self.noslip_tag), - bc_q_supersonic_in=self.bc_supersonic_inflow - .boundary_interpolant(t, discr, - self.supersonic_inflow_tag), - **extra_kwargs - ) - - max_speed = opt_result[-1] - ode_rhs = opt_result[:-1] - return ode_rhs, discr.nodewise_max(max_speed) - - return rhs - - # }}} - - # {{{ timestep estimation ------------------------------------------------- - - def estimate_timestep(self, discr, - stepper=None, stepper_class=None, stepper_args=None, - t=None, max_eigenvalue=None): - """Estimate the largest stable timestep, given a time stepper - `stepper_class`. If none is given, RK4 is assumed. - """ - - dg_factor = (discr.dt_non_geometric_factor() - * discr.dt_geometric_factor()) - - # see JSH/TW, eq. (7.32) - rk4_dt = dg_factor / (max_eigenvalue + self.mu / dg_factor) - - from grudge.timestep.stability import \ - approximate_rk4_relative_imag_stability_region - return rk4_dt * approximate_rk4_relative_imag_stability_region( - stepper, stepper_class, stepper_args) - - # }}} - - - - -# {{{ limiter (unfinished, deprecated) -class SlopeLimiter1NEuler: - def __init__(self, discr, gamma, dimensions, op): - """Construct a limiter from Jan's book page 225 - """ - self.discr = discr - self.gamma=gamma - self.dimensions=dimensions - self.op=op - - from grudge.symbolic.operators import AveragingOperator - self.get_average = AveragingOperator().bind(discr) - - def __call__(self, fields): - from grudge.tools import join_fields - - #get conserved fields - rho=self.op.rho(fields) - e=self.op.e(fields) - rho_velocity=self.op.rho_u(fields) - - #get primitive fields - #to do - - #reset field values to cell average - rhoLim=self.get_average(rho) - eLim=self.get_average(e) - temp=join_fields([self.get_average(rho_vel) - for rho_vel in rho_velocity]) - - #should do for primitive fields too - - return join_fields(rhoLim, eLim, temp) - -# }}} - - -# vim: foldmethod=marker diff --git a/grudge/models/gas_dynamics/lbm.py b/grudge/models/gas_dynamics/lbm.py deleted file mode 100644 index 548e38aaeb0422442362c8f910b705e6c842e01d..0000000000000000000000000000000000000000 --- a/grudge/models/gas_dynamics/lbm.py +++ /dev/null @@ -1,204 +0,0 @@ -"""Lattice-Boltzmann operator.""" - -__copyright__ = "Copyright (C) 2011 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -import numpy as np -import numpy.linalg as la -from grudge.models import HyperbolicOperator -from pytools.obj_array import make_obj_array - - -class LBMMethodBase: - def __len__(self): - return len(self.direction_vectors) - - def find_opposites(self): - self.opposites = np.zeros(len(self)) - - for alpha in range(len(self)): - if self.opposites[alpha]: - continue - - found = False - for alpha_2 in range(alpha, len(self)): - if la.norm( - self.direction_vectors[alpha] - + self.direction_vectors[alpha_2]) < 1e-12: - self.opposites[alpha] = alpha_2 - self.opposites[alpha_2] = alpha - found = True - - if not found: - raise RuntimeError( - "direction %s had no opposite" - % self.direction_vectors[alpha]) - - - - -class D2Q9LBMMethod(LBMMethodBase): - def __init__(self): - self.dimensions = 2 - - alphas = np.arange(0, 9) - thetas = (alphas-1)*np.pi/2 - thetas[5:9] += np.pi/4 - - direction_vectors = np.vstack([ - np.cos(thetas), np.sin(thetas)]).T - - direction_vectors[0] *= 0 - direction_vectors[5:9] *= np.sqrt(2) - - direction_vectors[np.abs(direction_vectors) < 1e-12] = 0 - - self.direction_vectors = direction_vectors - - self.weights = np.array([4/9] + [1/9]*4 + [1/36]*4) - - self.speed_of_sound = 1/np.sqrt(3) - self.find_opposites() - - def f_equilibrium(self, rho, alpha, u): - e_alpha = self.direction_vectors[alpha] - c_s = self.speed_of_sound - return self.weights[alpha]*rho*( - 1 - + np.dot(e_alpha, u)/c_s**2 - + 1/2*np.dot(e_alpha, u)**2/c_s**4 - - 1/2*np.dot(u, u)/c_s**2) - - - - -class LatticeBoltzmannOperator(HyperbolicOperator): - def __init__(self, method, lbm_delta_t, nu, flux_type="upwind"): - self.method = method - self.lbm_delta_t = lbm_delta_t - self.nu = nu - - self.flux_type = flux_type - - @property - def tau(self): - return (self.nu - / - (self.lbm_delta_t*self.method.speed_of_sound**2)) - - def get_advection_flux(self, velocity): - from grudge.flux import make_normal, FluxScalarPlaceholder - from pymbolic.primitives import IfPositive - - u = FluxScalarPlaceholder(0) - normal = make_normal(self.method.dimensions) - - if self.flux_type == "central": - return u.avg*np.dot(normal, velocity) - elif self.flux_type == "lf": - return u.avg*np.dot(normal, velocity) \ - + 0.5*la.norm(v)*(u.int - u.ext) - elif self.flux_type == "upwind": - return (np.dot(normal, velocity)* - IfPositive(np.dot(normal, velocity), - u.int, # outflow - u.ext, # inflow - )) - else: - raise ValueError("invalid flux type") - - def get_advection_op(self, q, velocity): - from grudge.symbolic import ( - BoundaryPair, - get_flux_operator, - make_stiffness_t, - InverseMassOperator) - - stiff_t = make_stiffness_t(self.method.dimensions) - - flux_op = get_flux_operator(self.get_advection_flux(velocity)) - return InverseMassOperator()( - np.dot(velocity, stiff_t*q) - flux_op(q)) - - def f_bar(self): - from grudge.symbolic import make_sym_vector - return make_sym_vector("f_bar", len(self.method)) - - def rho(self, f_bar): - return sum(f_bar) - - def rho_u(self, f_bar): - return sum( - dv_i * field_i - for dv_i, field_i in - zip(self.method.direction_vectors, f_bar)) - - def stream_rhs(self, f_bar): - return make_obj_array([ - self.get_advection_op(f_bar_alpha, e_alpha) - for e_alpha, f_bar_alpha in - zip(self.method.direction_vectors, f_bar)]) - - def collision_update(self, f_bar): - from grudge.symbolic.primitives import make_common_subexpression as cse - rho = cse(self.rho(f_bar), "rho") - rho_u = self.rho_u(f_bar) - u = cse(rho_u/rho, "u") - - f_eq_func = self.method.f_equilibrium - f_eq = make_obj_array([ - f_eq_func(rho, alpha, u) for alpha in range(len(self.method))]) - - return f_bar - 1/(self.tau+1/2)*(f_bar - f_eq) - - def bind_rhs(self, discr): - compiled_sym_operator = discr.compile( - self.stream_rhs(self.f_bar())) - - #from grudge.mesh import check_bc_coverage, BTAG_ALL - #check_bc_coverage(discr.mesh, [BTAG_ALL]) - - def rhs(t, f_bar): - return compiled_sym_operator(f_bar=f_bar) - - return rhs - - def bind(self, discr, what): - f_bar_sym = self.f_bar() - - from grudge.symbolic.mappers.type_inference import ( - type_info, NodalRepresentation) - - type_hints = { - f_bar_i: type_info.VolumeVector(NodalRepresentation()) - for f_bar_i in f_bar_sym} - - compiled_sym_operator = discr.compile(what(f_bar_sym), type_hints=type_hints) - - def rhs(f_bar): - return compiled_sym_operator(f_bar=f_bar) - - return rhs - - def max_eigenvalue(self, t=None, fields=None, discr=None): - return max( - la.norm(v) for v in self.method.direction_vectors) diff --git a/grudge/models/nd_calculus.py b/grudge/models/nd_calculus.py deleted file mode 100644 index bcfbd57d758723412dc9b4c73292759bab0ea2ae..0000000000000000000000000000000000000000 --- a/grudge/models/nd_calculus.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Canned operators for multivariable calculus.""" - -__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -from grudge.models import Operator - - -class GradientOperator(Operator): - def __init__(self, dimensions): - self.dimensions = dimensions - - def flux(self): - from grudge.flux import make_normal, FluxScalarPlaceholder - u = FluxScalarPlaceholder() - - normal = make_normal(self.dimensions) - return u.int*normal - u.avg*normal - - def sym_operator(self): - from grudge.mesh import BTAG_ALL - from grudge.symbolic import Field, BoundaryPair, \ - make_nabla, InverseMassOperator, get_flux_operator - - u = Field("u") - bc = Field("bc") - - nabla = make_nabla(self.dimensions) - flux_op = get_flux_operator(self.flux()) - - return nabla*u - InverseMassOperator()( - flux_op(u) + - flux_op(BoundaryPair(u, bc, BTAG_ALL))) - - def bind(self, discr): - compiled_sym_operator = discr.compile(self.op_template()) - - def op(u): - from grudge.mesh import BTAG_ALL - - return compiled_sym_operator(u=u, - bc=discr.boundarize_volume_field(u, BTAG_ALL)) - - return op - - - - -class DivergenceOperator(Operator): - def __init__(self, dimensions, subset=None): - self.dimensions = dimensions - - if subset is None: - self.subset = dimensions * [True,] - else: - # chop off any extra dimensions - self.subset = subset[:dimensions] - - from grudge.tools import count_subset - self.arg_count = count_subset(self.subset) - - def flux(self): - from grudge.flux import make_normal, FluxVectorPlaceholder - - v = FluxVectorPlaceholder(self.arg_count) - - normal = make_normal(self.dimensions) - - flux = 0 - idx = 0 - - for i, i_enabled in enumerate(self.subset): - if i_enabled and i < self.dimensions: - flux += (v.int-v.avg)[idx]*normal[i] - idx += 1 - - return flux - - def sym_operator(self): - from grudge.mesh import BTAG_ALL - from grudge.symbolic import make_sym_vector, BoundaryPair, \ - get_flux_operator, make_nabla, InverseMassOperator - - nabla = make_nabla(self.dimensions) - m_inv = InverseMassOperator() - - v = make_sym_vector("v", self.arg_count) - bc = make_sym_vector("bc", self.arg_count) - - local_op_result = 0 - idx = 0 - for i, i_enabled in enumerate(self.subset): - if i_enabled and i < self.dimensions: - local_op_result += nabla[i]*v[idx] - idx += 1 - - flux_op = get_flux_operator(self.flux()) - - return local_op_result - m_inv( - flux_op(v) + - flux_op(BoundaryPair(v, bc, BTAG_ALL))) - - def bind(self, discr): - compiled_sym_operator = discr.compile(self.op_template()) - - def op(v): - from grudge.mesh import BTAG_ALL - return compiled_sym_operator(v=v, - bc=discr.boundarize_volume_field(v, BTAG_ALL)) - - return op diff --git a/grudge/models/pml.py b/grudge/models/pml.py deleted file mode 100644 index bd8bfd0468ebc96ea23cbd691de0eaa3dee8344e..0000000000000000000000000000000000000000 --- a/grudge/models/pml.py +++ /dev/null @@ -1,279 +0,0 @@ -"""Models describing absorbing boundary layers.""" - -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -import numpy - -from pytools import memoize_method, Record -from grudge.models.em import \ - MaxwellOperator, \ - TMMaxwellOperator, \ - TEMaxwellOperator - - -class AbarbanelGottliebPMLMaxwellOperator(MaxwellOperator): - """Implements a PML as in - - [1] S. Abarbanel and D. Gottlieb, "On the construction and analysis of absorbing - layers in CEM," Applied Numerical Mathematics, vol. 27, 1998, S. 331-340. - (eq 3.7-3.11) - - [2] E. Turkel and A. Yefet, "Absorbing PML - boundary layers for wave-like equations," - Applied Numerical Mathematics, vol. 27, - 1998, S. 533-557. - (eq. 4.10) - - [3] Abarbanel, D. Gottlieb, and J.S. Hesthaven, "Long Time Behavior of the - Perfectly Matched Layer Equations in Computational Electromagnetics," - Journal of Scientific Computing, vol. 17, Dez. 2002, S. 405-422. - - Generalized to 3D in doc/maxima/abarbanel-pml.mac. - """ - - class PMLCoefficients(Record): - __slots__ = ["sigma", "sigma_prime", "tau"] - # (tau=mu in [3] , to avoid confusion with permeability) - - def map(self, f): - return self.__class__( - **{name: f(getattr(self, name)) - for name in self.fields}) - - def __init__(self, *args, **kwargs): - self.add_decay = kwargs.pop("add_decay", True) - MaxwellOperator.__init__(self, *args, **kwargs) - - def pml_local_op(self, w): - sub_e, sub_h, sub_p, sub_q = self.split_ehpq(w) - - e_subset = self.get_eh_subset()[0:3] - h_subset = self.get_eh_subset()[3:6] - dim_subset = (True,) * self.dimensions + (False,) * (3-self.dimensions) - - def pad_vec(v, subset): - result = numpy.zeros((3,), dtype=object) - result[numpy.array(subset, dtype=bool)] = v - return result - - from grudge.symbolic import make_sym_vector - sig = pad_vec( - make_sym_vector("sigma", self.dimensions), - dim_subset) - sig_prime = pad_vec( - make_sym_vector("sigma_prime", self.dimensions), - dim_subset) - if self.add_decay: - tau = pad_vec( - make_sym_vector("tau", self.dimensions), - dim_subset) - else: - tau = numpy.zeros((3,)) - - e = pad_vec(sub_e, e_subset) - h = pad_vec(sub_h, h_subset) - p = pad_vec(sub_p, dim_subset) - q = pad_vec(sub_q, dim_subset) - - rhs = numpy.zeros(12, dtype=object) - - for mx in range(3): - my = (mx+1) % 3 - mz = (mx+2) % 3 - - from grudge.tools.mathematics import levi_civita - assert levi_civita((mx,my,mz)) == 1 - - rhs[mx] += -sig[my]/self.epsilon*(2*e[mx]+p[mx]) - 2*tau[my]/self.epsilon*e[mx] - rhs[my] += -sig[mx]/self.epsilon*(2*e[my]+p[my]) - 2*tau[mx]/self.epsilon*e[my] - rhs[3+mz] += 1/(self.epsilon*self.mu) * ( - sig_prime[mx] * q[mx] - sig_prime[my] * q[my]) - - rhs[6+mx] += sig[my]/self.epsilon*e[mx] - rhs[6+my] += sig[mx]/self.epsilon*e[my] - rhs[9+mx] += -sig[mx]/self.epsilon*q[mx] - (e[my] + e[mz]) - - from grudge.tools import full_to_subset_indices - sub_idx = full_to_subset_indices(e_subset+h_subset+dim_subset+dim_subset) - - return rhs[sub_idx] - - def sym_operator(self, w=None): - from grudge.tools import count_subset - fld_cnt = count_subset(self.get_eh_subset()) - if w is None: - from grudge.symbolic import make_sym_vector - w = make_sym_vector("w", fld_cnt+2*self.dimensions) - - from grudge.tools import join_fields - return join_fields( - MaxwellOperator.sym_operator(self, w[:fld_cnt]), - numpy.zeros((2*self.dimensions,), dtype=object) - ) + self.pml_local_op(w) - - def bind(self, discr, coefficients): - return MaxwellOperator.bind(self, discr, - sigma=coefficients.sigma, - sigma_prime=coefficients.sigma_prime, - tau=coefficients.tau) - - def assemble_ehpq(self, e=None, h=None, p=None, q=None, discr=None): - if discr is None: - def zero(): - return 0 - else: - def zero(): - return discr.volume_zeros() - - from grudge.tools import count_subset - e_components = count_subset(self.get_eh_subset()[0:3]) - h_components = count_subset(self.get_eh_subset()[3:6]) - - def default_fld(fld, comp): - if fld is None: - return [zero() for i in range(comp)] - else: - return fld - - e = default_fld(e, e_components) - h = default_fld(h, h_components) - p = default_fld(p, self.dimensions) - q = default_fld(q, self.dimensions) - - from grudge.tools import join_fields - return join_fields(e, h, p, q) - - @memoize_method - def partial_to_ehpq_subsets(self): - e_subset = self.get_eh_subset()[0:3] - h_subset = self.get_eh_subset()[3:6] - - dim_subset = [True] * self.dimensions + [False] * (3-self.dimensions) - - from grudge.tools import partial_to_all_subset_indices - return tuple(partial_to_all_subset_indices( - [e_subset, h_subset, dim_subset, dim_subset])) - - def split_ehpq(self, w): - e_idx, h_idx, p_idx, q_idx = self.partial_to_ehpq_subsets() - e, h, p, q = w[e_idx], w[h_idx], w[p_idx], w[q_idx] - - from grudge.flux import FluxVectorPlaceholder as FVP - if isinstance(w, FVP): - return FVP(scalars=e), FVP(scalars=h) - else: - from grudge.tools import make_obj_array as moa - return moa(e), moa(h), moa(p), moa(q) - - # sigma business ---------------------------------------------------------- - def _construct_scalar_coefficients(self, discr, node_coord, - i_min, i_max, o_min, o_max, exponent): - assert o_min < i_min <= i_max < o_max - - if o_min != i_min: - l_dist = (i_min - node_coord) / (i_min-o_min) - l_dist_prime = discr.volume_zeros(kind="numpy", dtype=node_coord.dtype) - l_dist_prime[l_dist >= 0] = -1 / (i_min-o_min) - l_dist[l_dist < 0] = 0 - else: - l_dist = l_dist_prime = numpy.zeros_like(node_coord) - - if i_max != o_max: - r_dist = (node_coord - i_max) / (o_max-i_max) - r_dist_prime = discr.volume_zeros(kind="numpy", dtype=node_coord.dtype) - r_dist_prime[r_dist >= 0] = 1 / (o_max-i_max) - r_dist[r_dist < 0] = 0 - else: - r_dist = r_dist_prime = numpy.zeros_like(node_coord) - - l_plus_r = l_dist+r_dist - return l_plus_r**exponent, \ - (l_dist_prime+r_dist_prime)*exponent*l_plus_r**(exponent-1), \ - l_plus_r - - def coefficients_from_boxes(self, discr, - inner_bbox, outer_bbox=None, - magnitude=None, tau_magnitude=None, - exponent=None, dtype=None): - if outer_bbox is None: - outer_bbox = discr.mesh.bounding_box() - - if exponent is None: - exponent = 2 - - if magnitude is None: - magnitude = 20 - - if tau_magnitude is None: - tau_magnitude = 0.4 - - # scale by free space conductivity - from math import sqrt - magnitude = magnitude*sqrt(self.epsilon/self.mu) - tau_magnitude = tau_magnitude*sqrt(self.epsilon/self.mu) - - i_min, i_max = inner_bbox - o_min, o_max = outer_bbox - - from grudge.tools import make_obj_array - - nodes = discr.nodes - if dtype is not None: - nodes = nodes.astype(dtype) - - sigma, sigma_prime, tau = list(zip(*[self._construct_scalar_coefficients( - discr, nodes[:,i], - i_min[i], i_max[i], o_min[i], o_max[i], - exponent) - for i in range(discr.dimensions)])) - - def conv(f): - return discr.convert_volume(f, kind=discr.compute_kind, - dtype=discr.default_scalar_type) - - return self.PMLCoefficients( - sigma=conv(magnitude*make_obj_array(sigma)), - sigma_prime=conv(magnitude*make_obj_array(sigma_prime)), - tau=conv(tau_magnitude*make_obj_array(tau))) - - def coefficients_from_width(self, discr, width, - magnitude=None, tau_magnitude=None, exponent=None, - dtype=None): - o_min, o_max = discr.mesh.bounding_box() - return self.coefficients_from_boxes(discr, - (o_min+width, o_max-width), - (o_min, o_max), - magnitude, tau_magnitude, exponent, dtype) - - - - -class AbarbanelGottliebPMLTEMaxwellOperator( - TEMaxwellOperator, AbarbanelGottliebPMLMaxwellOperator): - # not unimplemented--this IS the implementation. - pass - -class AbarbanelGottliebPMLTMMaxwellOperator( - TMMaxwellOperator, AbarbanelGottliebPMLMaxwellOperator): - # not unimplemented--this IS the implementation. - pass diff --git a/grudge/models/wave.py b/grudge/models/wave.py index 5fc65a70e2235ea03d33ef95ad7e91e302f313c4..ed0d40198397f070f9fba35300a0b932e99a1497 100644 --- a/grudge/models/wave.py +++ b/grudge/models/wave.py @@ -1,6 +1,9 @@ """Wave equation operators.""" -__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2009 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,12 +25,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + import numpy as np + +from arraycontext.container.traversal import thaw + from grudge.models import HyperbolicOperator + from meshmode.mesh import BTAG_ALL, BTAG_NONE -from grudge import sym + from pytools.obj_array import flat_obj_array +import grudge.op as op + # {{{ constant-velocity @@ -49,16 +59,18 @@ class WeakWaveOperator(HyperbolicOperator): :math:`c` is assumed to be constant across all space. """ - def __init__(self, c, ambient_dim, source_f=0, + def __init__(self, dcoll, c, source_f=None, flux_type="upwind", dirichlet_tag=BTAG_ALL, dirichlet_bc_f=0, neumann_tag=BTAG_NONE, radiation_tag=BTAG_NONE): - assert isinstance(ambient_dim, int) + if source_f is None: + source_f = lambda actx, dcoll, t: dcoll.zeros(actx) # noqa: E731 + + self.dcoll = dcoll self.c = c - self.ambient_dim = ambient_dim self.source_f = source_f if self.c > 0: @@ -74,10 +86,11 @@ class WeakWaveOperator(HyperbolicOperator): self.flux_type = flux_type - def flux(self, w): - u = w[0] - v = w[1:] - normal = sym.normal(w.dd, self.ambient_dim) + def flux(self, wtpair): + u = wtpair[0] + v = wtpair[1:] + actx = u.int.array_context + normal = thaw(op.normal(self.dcoll, wtpair.dd), actx) central_flux_weak = -self.c*flat_obj_array( np.dot(v.avg, normal), @@ -92,65 +105,66 @@ class WeakWaveOperator(HyperbolicOperator): else: raise ValueError("invalid flux type '%s'" % self.flux_type) - def sym_operator(self): - d = self.ambient_dim - - w = sym.make_sym_array("w", d+1) + def operator(self, t, w): + dcoll = self.dcoll u = w[0] v = w[1:] + actx = u.array_context # boundary conditions ------------------------------------------------- # dirichlet BCs ------------------------------------------------------- - dir_u = sym.cse(sym.project("vol", self.dirichlet_tag)(u)) - dir_v = sym.cse(sym.project("vol", self.dirichlet_tag)(v)) + dir_u = op.project(dcoll, "vol", self.dirichlet_tag, u) + dir_v = op.project(dcoll, "vol", self.dirichlet_tag, v) if self.dirichlet_bc_f: # FIXME from warnings import warn warn("Inhomogeneous Dirichlet conditions on the wave equation " "are still having issues.") - dir_g = sym.var("dir_bc_u") + dir_g = self.dirichlet_bc_f dir_bc = flat_obj_array(2*dir_g - dir_u, dir_v) else: dir_bc = flat_obj_array(-dir_u, dir_v) - dir_bc = sym.cse(dir_bc, "dir_bc") - # neumann BCs --------------------------------------------------------- - neu_u = sym.cse(sym.project("vol", self.neumann_tag)(u)) - neu_v = sym.cse(sym.project("vol", self.neumann_tag)(v)) - neu_bc = sym.cse(flat_obj_array(neu_u, -neu_v), "neu_bc") + neu_u = op.project(dcoll, "vol", self.neumann_tag, u) + neu_v = op.project(dcoll, "vol", self.neumann_tag, v) + neu_bc = flat_obj_array(neu_u, -neu_v) # radiation BCs ------------------------------------------------------- - rad_normal = sym.normal(self.radiation_tag, d) + rad_normal = thaw(op.normal(dcoll, dd=self.radiation_tag), actx) - rad_u = sym.cse(sym.project("vol", self.radiation_tag)(u)) - rad_v = sym.cse(sym.project("vol", self.radiation_tag)(v)) + rad_u = op.project(dcoll, "vol", self.radiation_tag, u) + rad_v = op.project(dcoll, "vol", self.radiation_tag, v) - rad_bc = sym.cse(flat_obj_array( - 0.5*(rad_u - self.sign*np.dot(rad_normal, rad_v)), - 0.5*rad_normal*(np.dot(rad_normal, rad_v) - self.sign*rad_u) - ), "rad_bc") + rad_bc = flat_obj_array( + 0.5*(rad_u - self.sign*np.dot(rad_normal, rad_v)), + 0.5*rad_normal*(np.dot(rad_normal, rad_v) - self.sign*rad_u) + ) # entire operator ----------------------------------------------------- - def flux(pair): - return sym.project(pair.dd, "all_faces")(self.flux(pair)) + def flux(tpair): + return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) - result = sym.InverseMassOperator()( + result = ( + op.inverse_mass( + dcoll, flat_obj_array( - -self.c*np.dot(sym.stiffness_t(self.ambient_dim), v), - -self.c*(sym.stiffness_t(self.ambient_dim)*u) - ) - - - sym.FaceMassOperator()(flux(sym.int_tpair(w)) - + flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc)) - + flux(sym.bv_tpair(self.neumann_tag, w, neu_bc)) - + flux(sym.bv_tpair(self.radiation_tag, w, rad_bc)) - - )) - - result[0] += self.source_f + -self.c*op.weak_local_div(dcoll, v), + -self.c*op.weak_local_grad(dcoll, u) + ) + - op.face_mass( + dcoll, + sum(flux(tpair) for tpair in op.interior_trace_pairs(dcoll, w)) + + flux(op.bv_trace_pair(dcoll, self.dirichlet_tag, w, dir_bc)) + + flux(op.bv_trace_pair(dcoll, self.neumann_tag, w, neu_bc)) + + flux(op.bv_trace_pair(dcoll, self.radiation_tag, w, rad_bc)) + ) + ) + ) + + result[0] += self.source_f(actx, dcoll, t) return result @@ -188,21 +202,23 @@ class VariableCoefficientWeakWaveOperator(HyperbolicOperator): :math:`c` is assumed to be constant across all space. """ - def __init__(self, c, ambient_dim, source_f=0, + def __init__(self, dcoll, c, source_f=None, flux_type="upwind", dirichlet_tag=BTAG_ALL, dirichlet_bc_f=0, neumann_tag=BTAG_NONE, radiation_tag=BTAG_NONE): - assert isinstance(ambient_dim, int) + if source_f is None: + source_f = lambda actx, dcoll, t: dcoll.zeros(actx) # noqa: E731 + + self.dcoll = dcoll self.c = c - self.ambient_dim = ambient_dim self.source_f = source_f - self.sign = sym.If(sym.Comparison( - self.c, ">", 0), - np.int32(1), np.int32(-1)) + actx = dcoll._setup_actx + ones = dcoll.zeros(actx) + 1 + self.sign = actx.np.where(self.c > 0, ones, -ones) self.dirichlet_tag = dirichlet_tag self.neumann_tag = neumann_tag @@ -212,11 +228,12 @@ class VariableCoefficientWeakWaveOperator(HyperbolicOperator): self.flux_type = flux_type - def flux(self, w): - c = w[0] - u = w[1] - v = w[2:] - normal = sym.normal(w.dd, self.ambient_dim) + def flux(self, wtpair): + c = wtpair[0] + u = wtpair[1] + v = wtpair[2:] + actx = u.int.array_context + normal = thaw(op.normal(self.dcoll, wtpair.dd), actx) flux_central_weak = -0.5 * flat_obj_array( np.dot(v.int*c.int + v.ext*c.ext, normal), @@ -234,71 +251,76 @@ class VariableCoefficientWeakWaveOperator(HyperbolicOperator): else: raise ValueError("invalid flux type '%s'" % self.flux_type) - def sym_operator(self): - d = self.ambient_dim - - w = sym.make_sym_array("w", d+1) + def operator(self, t, w): + dcoll = self.dcoll u = w[0] v = w[1:] flux_w = flat_obj_array(self.c, w) + actx = u.array_context # boundary conditions ------------------------------------------------- # dirichlet BCs ------------------------------------------------------- - dir_c = sym.cse(sym.project("vol", self.dirichlet_tag)(self.c)) - dir_u = sym.cse(sym.project("vol", self.dirichlet_tag)(u)) - dir_v = sym.cse(sym.project("vol", self.dirichlet_tag)(v)) + dir_c = op.project(dcoll, "vol", self.dirichlet_tag, self.c) + dir_u = op.project(dcoll, "vol", self.dirichlet_tag, u) + dir_v = op.project(dcoll, "vol", self.dirichlet_tag, v) if self.dirichlet_bc_f: # FIXME from warnings import warn warn("Inhomogeneous Dirichlet conditions on the wave equation " "are still having issues.") - dir_g = sym.var("dir_bc_u") + dir_g = self.dirichlet_bc_f dir_bc = flat_obj_array(dir_c, 2*dir_g - dir_u, dir_v) else: dir_bc = flat_obj_array(dir_c, -dir_u, dir_v) - dir_bc = sym.cse(dir_bc, "dir_bc") - # neumann BCs --------------------------------------------------------- - neu_c = sym.cse(sym.project("vol", self.neumann_tag)(self.c)) - neu_u = sym.cse(sym.project("vol", self.neumann_tag)(u)) - neu_v = sym.cse(sym.project("vol", self.neumann_tag)(v)) - neu_bc = sym.cse(flat_obj_array(neu_c, neu_u, -neu_v), "neu_bc") + neu_c = op.project(dcoll, "vol", self.neumann_tag, self.c) + neu_u = op.project(dcoll, "vol", self.neumann_tag, u) + neu_v = op.project(dcoll, "vol", self.neumann_tag, v) + neu_bc = flat_obj_array(neu_c, neu_u, -neu_v) # radiation BCs ------------------------------------------------------- - rad_normal = sym.normal(self.radiation_tag, d) + rad_normal = thaw(op.normal(dcoll, dd=self.radiation_tag), actx) - rad_c = sym.cse(sym.project("vol", self.radiation_tag)(self.c)) - rad_u = sym.cse(sym.project("vol", self.radiation_tag)(u)) - rad_v = sym.cse(sym.project("vol", self.radiation_tag)(v)) + rad_c = op.project(dcoll, "vol", self.radiation_tag, self.c) + rad_u = op.project(dcoll, "vol", self.radiation_tag, u) + rad_v = op.project(dcoll, "vol", self.radiation_tag, v) + rad_sign = op.project(dcoll, "vol", self.radiation_tag, self.sign) - rad_bc = sym.cse(flat_obj_array(rad_c, - 0.5*(rad_u - sym.project("vol", self.radiation_tag)(self.sign) - * np.dot(rad_normal, rad_v)), - 0.5*rad_normal*(np.dot(rad_normal, rad_v) - - sym.project("vol", self.radiation_tag)(self.sign)*rad_u) - ), "rad_bc") + rad_bc = flat_obj_array( + rad_c, + 0.5*(rad_u - rad_sign * np.dot(rad_normal, rad_v)), + 0.5*rad_normal*(np.dot(rad_normal, rad_v) - rad_sign*rad_u) + ) # entire operator ----------------------------------------------------- - def flux(pair): - return sym.project(pair.dd, "all_faces")(self.flux(pair)) + def flux(tpair): + return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) - result = sym.InverseMassOperator()( + result = ( + op.inverse_mass( + dcoll, flat_obj_array( - -self.c*np.dot(sym.stiffness_t(self.ambient_dim), v), - -self.c*(sym.stiffness_t(self.ambient_dim)*u) - ) - - - sym.FaceMassOperator()(flux(sym.int_tpair(flux_w)) - + flux(sym.bv_tpair(self.dirichlet_tag, flux_w, dir_bc)) - + flux(sym.bv_tpair(self.neumann_tag, flux_w, neu_bc)) - + flux(sym.bv_tpair(self.radiation_tag, flux_w, rad_bc)) - - )) - - result[0] += self.source_f + -self.c*op.weak_local_div(dcoll, v), + -self.c*op.weak_local_grad(dcoll, u) + ) + - op.face_mass( + dcoll, + sum(flux(tpair) + for tpair in op.interior_trace_pairs(dcoll, flux_w)) + + flux(op.bv_trace_pair(dcoll, self.dirichlet_tag, + flux_w, dir_bc)) + + flux(op.bv_trace_pair(dcoll, self.neumann_tag, + flux_w, neu_bc)) + + flux(op.bv_trace_pair(dcoll, self.radiation_tag, + flux_w, rad_bc)) + ) + ) + ) + + result[0] += self.source_f(actx, dcoll, t) return result @@ -310,7 +332,8 @@ class VariableCoefficientWeakWaveOperator(HyperbolicOperator): self.radiation_tag]) def max_eigenvalue(self, t, fields=None, discr=None): - return sym.NodalMax("vol")(sym.fabs(self.c)) + actx = self.dcoll._setup_actx + return op.nodal_max(self.dcoll, "vol", actx.np.fabs(self.c)) # }}} diff --git a/grudge/op.py b/grudge/op.py index 6efde43f70f18bf1004e357265fc38c86cf8d64e..3929503f323d51a55f054a982e05ae7265d417f0 100644 --- a/grudge/op.py +++ b/grudge/op.py @@ -1,30 +1,66 @@ """ +Data transfer and geometry +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Projection and interpolation +---------------------------- + .. autofunction:: project + +Geometric quantities +-------------------- + .. autofunction:: nodes +.. autofunction:: normal +.. autofunction:: h_max_from_volume +.. autofunction:: h_min_from_volume + +Core DG routines +^^^^^^^^^^^^^^^^ + +Elementwise differentiation +--------------------------- .. autofunction:: local_grad .. autofunction:: local_d_dx .. autofunction:: local_div +Weak derivative operators +------------------------- + .. autofunction:: weak_local_grad .. autofunction:: weak_local_d_dx .. autofunction:: weak_local_div -.. autofunction:: normal +Mass, inverse mass, and face mass operators +------------------------------------------- + .. autofunction:: mass .. autofunction:: inverse_mass .. autofunction:: face_mass +Support functions +^^^^^^^^^^^^^^^^^ + +Nodal reductions +---------------- + .. autofunction:: norm .. autofunction:: nodal_sum .. autofunction:: nodal_min .. autofunction:: nodal_max +.. autofunction:: integral + +Elementwise reductions +---------------------- -.. autofunction:: interior_trace_pair -.. autofunction:: cross_rank_trace_pairs +.. autofunction:: elementwise_sum """ -__copyright__ = "Copyright (C) 2021 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2021 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -48,37 +84,74 @@ THE SOFTWARE. from numbers import Number -from pytools import memoize_on_first_arg - -import numpy as np # noqa +from functools import reduce + +from arraycontext import ( + ArrayContext, + FirstAxisIsElementsTag, + make_loopy_program, + freeze +) + +from grudge.discretization import DiscretizationCollection + +from pytools import ( + memoize_in, + memoize_on_first_arg, + keyed_memoize_in +) from pytools.obj_array import obj_array_vectorize, make_obj_array -import pyopencl.array as cla # noqa -from grudge import sym, bind +from meshmode.dof_array import DOFArray + +import numpy as np import grudge.dof_desc as dof_desc -from meshmode.mesh import BTAG_ALL, BTAG_NONE, BTAG_PARTITION # noqa -from meshmode.dof_array import freeze, flatten, unflatten +from grudge.trace_pair import ( # noqa + interior_trace_pair, + interior_trace_pairs, + connected_ranks, + cross_rank_trace_pairs, + bdry_trace_pair, + bv_trace_pair +) + -from grudge.symbolic.primitives import TracePair +# {{{ Kernel tags +class HasElementwiseMatvecTag(FirstAxisIsElementsTag): + """A tag that is applicable to kernel programs indicating that + an element-wise matrix product is being performed. This indicates + that the first index corresponds to element indices and suggests that + the implementation should set element indices as the outermost + loop extent. + """ + +# }}} -# def interp(dcoll, src, tgt, vec): + +# {{{ Interpolation and projection + +# FIXME: Should reintroduce interp and make clear distinctions +# between projection and interpolations. +# Related issue: https://github.com/inducer/grudge/issues/38 +# def interp(dcoll: DiscretizationCollection, src, tgt, vec): # from warnings import warn # warn("using 'interp' is deprecated, use 'project' instead.", -# DeprecationWarning, stacklevel=2) +# DeprecationWarning, stacklevel=2) # -# return dcoll.project(src, tgt, vec) +# return project(dcoll, src, tgt, vec) -def project(dcoll, src, tgt, vec): +def project(dcoll: DiscretizationCollection, src, tgt, vec): """Project from one discretization to another, e.g. from the volume to the boundary, or from the base to the an overintegrated quadrature discretization. - :arg src: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one - :arg tgt: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one - :arg vec: a :class:`~meshmode.dof_array.DOFArray` + :arg src: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + :arg tgt: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or a + :class:`~arraycontext.ArrayContainer`. """ src = dof_desc.as_dofdesc(src) tgt = dof_desc.as_dofdesc(tgt) @@ -94,57 +167,155 @@ def project(dcoll, src, tgt, vec): return dcoll.connection_from_dds(src, tgt)(vec) +# }}} + # {{{ geometric properties -def nodes(dcoll, dd=None): +def nodes(dcoll: DiscretizationCollection, dd=None) -> np.ndarray: r"""Return the nodes of a discretization. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization. - :returns: an object array of :class:`~meshmode.dof_array.DOFArray`\ s + :returns: an object array of :class:`~meshmode.dof_array.DOFArray`\ s. """ if dd is None: - return dcoll._volume_discr.nodes() - else: - return dcoll.discr_from_dd(dd).nodes() + dd = dof_desc.DD_VOLUME + dd = dof_desc.as_dofdesc(dd) + + return dcoll.discr_from_dd(dd).nodes() @memoize_on_first_arg -def normal(dcoll, dd): - """Get unit normal to specified surface discretization, *dd*. +def normal(dcoll: DiscretizationCollection, dd) -> np.ndarray: + r"""Get the unit normal to the specified surface discretization, *dd*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization. - :returns: an object array of :class:`~meshmode.dof_array.DOFArray`. + :returns: an object array of :class:`~meshmode.dof_array.DOFArray`\ s. """ - surface_discr = dcoll.discr_from_dd(dd) - actx = surface_discr._setup_actx - return freeze( - bind(dcoll, - sym.normal(dd, surface_discr.ambient_dim, surface_discr.dim), - local_only=True) - (array_context=actx)) + from grudge.geometry import normal -# }}} + actx = dcoll.discr_from_dd(dd)._setup_actx + return freeze(normal(actx, dcoll, dd)) -# {{{ derivatives +@memoize_on_first_arg +def h_max_from_volume(dcoll: DiscretizationCollection, dim=None, dd=None): + """Returns a (maximum) characteristic length based on the volume of the + elements. This length may not be representative if the elements have very + high aspect ratios. + + :arg dim: an integer denoting topological dimension. If *None*, the + spatial dimension specified by + :attr:`grudge.DiscretizationCollection.dim` is used. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization if not provided. + :returns: a scalar denoting the maximum characteristic length. + """ + if dd is None: + dd = dof_desc.DD_VOLUME + dd = dof_desc.as_dofdesc(dd) + + if dim is None: + dim = dcoll.dim + + ones = dcoll.discr_from_dd(dd).zeros(dcoll._setup_actx) + 1.0 + return nodal_max( + dcoll, + dd, + elementwise_sum(dcoll, mass(dcoll, dd, ones)) + ) ** (1.0 / dim) + @memoize_on_first_arg -def _bound_grad(dcoll): - return bind(dcoll, sym.nabla(dcoll.dim) * sym.Variable("u"), local_only=True) +def h_min_from_volume(dcoll: DiscretizationCollection, dim=None, dd=None): + """Returns a (minimum) characteristic length based on the volume of the + elements. This length may not be representative if the elements have very + high aspect ratios. + + :arg dim: an integer denoting topological dimension. If *None*, the + spatial dimension specified by + :attr:`grudge.DiscretizationCollection.dim` is used. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization if not provided. + :returns: a scalar denoting the minimum characteristic length. + """ + if dd is None: + dd = dof_desc.DD_VOLUME + dd = dof_desc.as_dofdesc(dd) + + if dim is None: + dim = dcoll.dim + + ones = dcoll.discr_from_dd(dd).zeros(dcoll._setup_actx) + 1.0 + return nodal_min( + dcoll, + dd, + elementwise_sum(dcoll, mass(dcoll, dd, ones)) + ) ** (1.0 / dim) + +# }}} + + +# {{{ Derivative operators + +def reference_derivative_matrices(actx: ArrayContext, element_group): + @keyed_memoize_in( + actx, reference_derivative_matrices, + lambda grp: grp.discretization_key()) + def get_ref_derivative_mats(grp): + from meshmode.discretization.poly_element import diff_matrices + return actx.freeze( + actx.from_numpy( + np.asarray( + [dfmat for dfmat in diff_matrices(grp)] + ) + ) + ) + return get_ref_derivative_mats(element_group) -def local_grad(dcoll, vec, *, nested=False): - r"""Return the element-local gradient of the volume function represented by - *vec*. +def _compute_local_gradient(dcoll: DiscretizationCollection, vec, xyz_axis): + from grudge.geometry import inverse_surface_metric_derivative + + discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) + actx = vec.array_context + + inverse_jac_t = actx.np.stack( + [inverse_surface_metric_derivative(actx, dcoll, rst_axis, xyz_axis) + for rst_axis in range(dcoll.dim)] + ) + return DOFArray( + actx, + data=tuple( + actx.einsum("dei,dij,ej->ei", + inv_jac_t_i, + reference_derivative_matrices(actx, grp), + vec_i, + arg_names=("inv_jac_t", "ref_diff_mat", "vec"), + tagged=(HasElementwiseMatvecTag(),)) + + for grp, vec_i, inv_jac_t_i in zip(discr.groups, vec, inverse_jac_t) + ) + ) + + +def local_grad( + dcoll: DiscretizationCollection, vec, *, nested=False) -> np.ndarray: + r"""Return the element-local gradient of a function :math:`f` represented + by *vec*: + + .. math:: + + \nabla|_E f = \left( + \partial_x|_E f, \partial_y|_E f, \partial_z|_E f \right) :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of - `~meshmode.dof_array.DOFArray` + :class:`~meshmode.dof_array.DOFArray`\ s. :arg nested: return nested object arrays instead of a single multidimensional - array if *vec* is non-scalar + array if *vec* is non-scalar. :returns: an object array (possibly nested) of - :class:`~meshmode.dof_array.DOFArray`\ s + :class:`~meshmode.dof_array.DOFArray`\ s. """ if isinstance(vec, np.ndarray): grad = obj_array_vectorize( @@ -154,28 +325,27 @@ def local_grad(dcoll, vec, *, nested=False): else: return np.stack(grad, axis=0) - return _bound_grad(dcoll)(u=vec) + return make_obj_array([_compute_local_gradient(dcoll, vec, xyz_axis) + for xyz_axis in range(dcoll.dim)]) -@memoize_on_first_arg -def _bound_d_dx(dcoll, xyz_axis): - return bind(dcoll, sym.nabla(dcoll.dim)[xyz_axis] * sym.Variable("u"), - local_only=True) +def local_d_dx(dcoll: DiscretizationCollection, xyz_axis, vec): + r"""Return the element-local derivative along axis *xyz_axis* of a + function :math:`f` represented by *vec*: + .. math:: -def local_d_dx(dcoll, xyz_axis, vec): - r"""Return the element-local derivative along axis *xyz_axis* of the volume - function represented by *vec*. + \frac{\partial f}{\partial \lbrace x,y,z\rbrace}\Big|_E :arg xyz_axis: an integer indicating the axis along which the derivative - is taken - :arg vec: a :class:`~meshmode.dof_array.DOFArray` - :returns: a :class:`~meshmode.dof_array.DOFArray`\ s + is taken. + :arg vec: a :class:`~meshmode.dof_array.DOFArray`. + :returns: a :class:`~meshmode.dof_array.DOFArray`\ s. """ - return _bound_d_dx(dcoll, xyz_axis)(u=vec) + return _compute_local_gradient(dcoll, vec, xyz_axis) -def _div_helper(dcoll, diff_func, vecs): +def _div_helper(dcoll: DiscretizationCollection, diff_func, vecs): if not isinstance(vecs, np.ndarray): raise TypeError("argument must be an object array") assert vecs.dtype == object @@ -198,43 +368,134 @@ def _div_helper(dcoll, diff_func, vecs): return result -def local_div(dcoll, vecs): - r"""Return the element-local divergence of the vector volume function - represented by *vecs*. +def local_div(dcoll: DiscretizationCollection, vecs): + r"""Return the element-local divergence of the vector function + :math:`\mathbf{f}` represented by *vecs*: + + .. math:: + + \nabla|_E \cdot \mathbf{f} = \sum_{i=1}^d \partial_{x_i}|_E \mathbf{f}_i :arg vec: an object array of a :class:`~meshmode.dof_array.DOFArray`\ s, where the last axis of the array must have length matching the volume dimension. - :returns: a :class:`~meshmode.dof_array.DOFArray` + :returns: a :class:`~meshmode.dof_array.DOFArray`. """ return _div_helper(dcoll, lambda i, subvec: local_d_dx(dcoll, i, subvec), vecs) - -@memoize_on_first_arg -def _bound_weak_grad(dcoll, dd): - return bind(dcoll, - sym.stiffness_t(dcoll.dim, dd_in=dd) * sym.Variable("u", dd), - local_only=True) +# }}} -def weak_local_grad(dcoll, *args, nested=False): +# {{{ Weak derivative operators + +def reference_stiffness_transpose_matrix( + actx: ArrayContext, out_element_group, in_element_group): + @keyed_memoize_in( + actx, reference_stiffness_transpose_matrix, + lambda out_grp, in_grp: (out_grp.discretization_key(), + in_grp.discretization_key())) + def get_ref_stiffness_transpose_mat(out_grp, in_grp): + if in_grp == out_grp: + from meshmode.discretization.poly_element import \ + mass_matrix, diff_matrices + + mmat = mass_matrix(out_grp) + return actx.freeze( + actx.from_numpy( + np.asarray( + [dmat.T @ mmat.T for dmat in diff_matrices(out_grp)] + ) + ) + ) + + from modepy import vandermonde + basis = out_grp.basis_obj() + vand = vandermonde(basis.functions, out_grp.unit_nodes) + grad_vand = vandermonde(basis.gradients, in_grp.unit_nodes) + vand_inv_t = np.linalg.inv(vand).T + + if not isinstance(grad_vand, tuple): + # NOTE: special case for 1d + grad_vand = (grad_vand,) + + weights = in_grp.quadrature_rule().weights + return actx.freeze( + actx.from_numpy( + np.einsum( + "c,bz,acz->abc", + weights, + vand_inv_t, + grad_vand + ).copy() # contigify the array + ) + ) + return get_ref_stiffness_transpose_mat(out_element_group, + in_element_group) + + +def _apply_stiffness_transpose_operator( + dcoll: DiscretizationCollection, dd_out, dd_in, vec, xyz_axis): + from grudge.geometry import \ + inverse_surface_metric_derivative, area_element + + in_discr = dcoll.discr_from_dd(dd_in) + out_discr = dcoll.discr_from_dd(dd_out) + + actx = vec.array_context + area_elements = area_element(actx, dcoll, dd=dd_in) + inverse_jac_t = actx.np.stack( + [inverse_surface_metric_derivative(actx, dcoll, + rst_axis, xyz_axis, dd=dd_in) + for rst_axis in range(dcoll.dim)] + ) + return DOFArray( + actx, + data=tuple( + actx.einsum("dij,ej,ej,dej->ei", + reference_stiffness_transpose_matrix( + actx, + out_element_group=out_grp, + in_element_group=in_grp + ), + ae_i, + vec_i, + inv_jac_t_i, + arg_names=("ref_stiffT_mat", "jac", "vec", "inv_jac_t"), + tagged=(HasElementwiseMatvecTag(),)) + + for out_grp, in_grp, vec_i, ae_i, inv_jac_t_i in zip(out_discr.groups, + in_discr.groups, + vec, + area_elements, + inverse_jac_t) + ) + ) + + +def weak_local_grad(dcoll: DiscretizationCollection, *args, nested=False): r"""Return the element-local weak gradient of the volume function represented by *vec*. May be called with ``(vecs)`` or ``(dd, vecs)``. + Specifically, the function returns an object array where the :math:`i`-th + component is the weak derivative with respect to the :math:`i`-th coordinate + of a scalar function :math:`f`. See :func:`weak_local_d_dx` for further + information. For non-scalar :math:`f`, the function will return a nested object + array containing the component-wise weak derivatives. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization if not provided. :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of - `~meshmode.dof_array.DOFArray` + :class:`~meshmode.dof_array.DOFArray`\ s. :arg nested: return nested object arrays instead of a single multidimensional array if *vec* is non-scalar :returns: an object array (possibly nested) of - :class:`~meshmode.dof_array.DOFArray`\ s + :class:`~meshmode.dof_array.DOFArray`\ s. """ if len(args) == 1: vec, = args @@ -252,27 +513,39 @@ def weak_local_grad(dcoll, *args, nested=False): else: return np.stack(grad, axis=0) - return _bound_weak_grad(dcoll, dd)(u=vec) + return make_obj_array( + [_apply_stiffness_transpose_operator(dcoll, + dof_desc.DD_VOLUME, + dd, vec, xyz_axis) + for xyz_axis in range(dcoll.dim)] + ) -@memoize_on_first_arg -def _bound_weak_d_dx(dcoll, dd, xyz_axis): - return bind(dcoll, - sym.stiffness_t(dcoll.dim, dd_in=dd)[xyz_axis] - * sym.Variable("u", dd), - local_only=True) - - -def weak_local_d_dx(dcoll, *args): +def weak_local_d_dx(dcoll: DiscretizationCollection, *args): r"""Return the element-local weak derivative along axis *xyz_axis* of the volume function represented by *vec*. May be called with ``(xyz_axis, vecs)`` or ``(dd, xyz_axis, vecs)``. + Specifically, this function computes the volume contribution of the + weak derivative in the :math:`i`-th component (specified by *xyz_axis*) + of a function :math:`f`, in each element :math:`E`, with respect to polynomial + test functions :math:`\phi`: + + .. math:: + + \int_E \partial_i\phi\,f\,\mathrm{d}x \sim + \mathbf{D}_{E,i}^T \mathbf{M}_{E}^T\mathbf{f}|_E, + + where :math:`\mathbf{D}_{E,i}` is the polynomial differentiation matrix on + an :math:`E` for the :math:`i`-th spatial coordinate, :math:`\mathbf{M}_E` + is the elemental mass matrix (see :func:`mass` for more information), and + :math:`\mathbf{f}|_E` is a vector of coefficients for :math:`f` on :math:`E`. + :arg xyz_axis: an integer indicating the axis along which the derivative is taken - :arg vec: a :class:`~meshmode.dof_array.DOFArray` - :returns: a :class:`~meshmode.dof_array.DOFArray`\ s + :arg vec: a :class:`~meshmode.dof_array.DOFArray`. + :returns: a :class:`~meshmode.dof_array.DOFArray`\ s. """ if len(args) == 2: xyz_axis, vec = args @@ -282,22 +555,37 @@ def weak_local_d_dx(dcoll, *args): else: raise TypeError("invalid number of arguments") - return _bound_weak_d_dx(dcoll, dd, xyz_axis)(u=vec) + return _apply_stiffness_transpose_operator(dcoll, + dof_desc.DD_VOLUME, + dd, vec, xyz_axis) -def weak_local_div(dcoll, *args): +def weak_local_div(dcoll: DiscretizationCollection, *args): r"""Return the element-local weak divergence of the vector volume function represented by *vecs*. May be called with ``(vecs)`` or ``(dd, vecs)``. + Specifically, this function computes the volume contribution of the + weak divergence of a vector function :math:`\mathbf{f}`, in each element + :math:`E`, with respect to polynomial test functions :math:`\phi`: + + .. math:: + + \int_E \nabla \phi \cdot \mathbf{f}\,\mathrm{d}x \sim + \sum_{i=1}^d \mathbf{D}_{E,i}^T \mathbf{M}_{E}^T\mathbf{f}_i|_E, + + where :math:`\mathbf{D}_{E,i}` is the polynomial differentiation matrix on + an :math:`E` for the :math:`i`-th spatial coordinate, and :math:`\mathbf{M}_E` + is the elemental mass matrix (see :func:`mass` for more information). + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization if not provided. :arg vec: a object array of a :class:`~meshmode.dof_array.DOFArray`\ s, where the last axis of the array must have length matching the volume dimension. - :returns: a :class:`~meshmode.dof_array.DOFArray` + :returns: a :class:`~meshmode.dof_array.DOFArray`. """ if len(args) == 1: vecs, = args @@ -314,15 +602,108 @@ def weak_local_div(dcoll, *args): # }}} -# {{{ mass-like +# {{{ Mass operator -@memoize_on_first_arg -def _bound_mass(dcoll, dd): - return bind(dcoll, sym.MassOperator(dd_in=dd)(sym.Variable("u", dd)), - local_only=True) +def reference_mass_matrix(actx: ArrayContext, out_element_group, in_element_group): + @keyed_memoize_in( + actx, reference_mass_matrix, + lambda out_grp, in_grp: (out_grp.discretization_key(), + in_grp.discretization_key())) + def get_ref_mass_mat(out_grp, in_grp): + if out_grp == in_grp: + from meshmode.discretization.poly_element import mass_matrix + + return actx.freeze( + actx.from_numpy( + np.asarray( + mass_matrix(out_grp), + order="C" + ) + ) + ) + + from modepy import vandermonde + basis = out_grp.basis_obj() + vand = vandermonde(basis.functions, out_grp.unit_nodes) + o_vand = vandermonde(basis.functions, in_grp.unit_nodes) + vand_inv_t = np.linalg.inv(vand).T + + weights = in_grp.quadrature_rule().weights + return actx.freeze( + actx.from_numpy( + np.asarray( + np.einsum("j,ik,jk->ij", weights, vand_inv_t, o_vand), + order="C" + ) + ) + ) + + return get_ref_mass_mat(out_element_group, in_element_group) + + +def _apply_mass_operator( + dcoll: DiscretizationCollection, dd_out, dd_in, vec): + if isinstance(vec, np.ndarray): + return obj_array_vectorize( + lambda vi: _apply_mass_operator(dcoll, + dd_out, + dd_in, vi), vec + ) + + from grudge.geometry import area_element + + in_discr = dcoll.discr_from_dd(dd_in) + out_discr = dcoll.discr_from_dd(dd_out) + + actx = vec.array_context + area_elements = area_element(actx, dcoll, dd=dd_in) + return DOFArray( + actx, + data=tuple( + actx.einsum("ij,ej,ej->ei", + reference_mass_matrix( + actx, + out_element_group=out_grp, + in_element_group=in_grp + ), + ae_i, + vec_i, + arg_names=("mass_mat", "jac", "vec"), + tagged=(HasElementwiseMatvecTag(),)) + + for in_grp, out_grp, ae_i, vec_i in zip( + in_discr.groups, out_discr.groups, area_elements, vec) + ) + ) + + +def mass(dcoll: DiscretizationCollection, *args): + r"""Return the action of the DG mass matrix on a vector (or vectors) + of :class:`~meshmode.dof_array.DOFArray`\ s, *vec*. In the case of + *vec* being an object array of :class:`~meshmode.dof_array.DOFArray`\ s, + the mass operator is applied in the Kronecker sense (component-wise). + + May be called with ``(vec)`` or ``(dd, vec)``. + + Specifically, this function applies the mass matrix elementwise on a + vector of coefficients :math:`\mathbf{f}` via: + :math:`\mathbf{M}_{E}\mathbf{f}|_E`, where + + .. math:: + + \left(\mathbf{M}_{E}\right)_{ij} = \int_E \phi_i \cdot \phi_j\,\mathrm{d}x, + where :math:`\phi_i` are local polynomial basis functions on :math:`E`. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization if not provided. + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + :returns: a :class:`~meshmode.dof_array.DOFArray` denoting the + application of the mass matrix, or an object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + """ -def mass(dcoll, *args): if len(args) == 1: vec, = args dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) @@ -331,34 +712,326 @@ def mass(dcoll, *args): else: raise TypeError("invalid number of arguments") - if isinstance(vec, np.ndarray): - return obj_array_vectorize( - lambda el: mass(dcoll, dd, el), vec) + return _apply_mass_operator(dcoll, dof_desc.DD_VOLUME, dd, vec) - return _bound_mass(dcoll, dd)(u=vec) +# }}} -@memoize_on_first_arg -def _bound_inverse_mass(dcoll): - return bind(dcoll, sym.InverseMassOperator()(sym.Variable("u")), - local_only=True) +# {{{ Mass inverse operator + +def reference_inverse_mass_matrix(actx: ArrayContext, element_group): + @keyed_memoize_in( + actx, reference_inverse_mass_matrix, + lambda grp: grp.discretization_key()) + def get_ref_inv_mass_mat(grp): + from modepy import inverse_mass_matrix + basis = grp.basis_obj() + + return actx.freeze( + actx.from_numpy( + np.asarray( + inverse_mass_matrix(basis.functions, grp.unit_nodes), + order="C" + ) + ) + ) + + return get_ref_inv_mass_mat(element_group) -def inverse_mass(dcoll, vec): +def _apply_inverse_mass_operator( + dcoll: DiscretizationCollection, dd_out, dd_in, vec): if isinstance(vec, np.ndarray): return obj_array_vectorize( - lambda el: inverse_mass(dcoll, el), vec) + lambda vi: _apply_inverse_mass_operator(dcoll, + dd_out, + dd_in, vi), vec + ) + + from grudge.geometry import area_element + + if dd_out != dd_in: + raise ValueError( + "Cannot compute inverse of a mass matrix mapping " + "between different element groups; inverse is not " + "guaranteed to be well-defined" + ) + + actx = vec.array_context + discr = dcoll.discr_from_dd(dd_in) + inv_area_elements = 1./area_element(actx, dcoll, dd=dd_in) + group_data = [] + for grp, jac_inv, vec_i in zip(discr.groups, inv_area_elements, vec): + + ref_mass_inverse = reference_inverse_mass_matrix(actx, + element_group=grp) + + # NOTE: Some discretizations can have both affine and non-affine + # groups. For example, discretizations on hybrid simplex-hex meshes. + if not grp.is_affine: + group_data.append( + # Based on https://arxiv.org/pdf/1608.03836.pdf + # true_Minv ~ ref_Minv * ref_M * (1/jac_det) * ref_Minv + actx.einsum("ik,km,em,mj,ej->ei", + # FIXME: Should we manually create a temporary for + # the mass inverse? + ref_mass_inverse, + reference_mass_matrix( + actx, + out_element_group=grp, + in_element_group=grp + ), + jac_inv, + # FIXME: Should we manually create a temporary for + # the mass inverse? + ref_mass_inverse, + vec_i, + tagged=(HasElementwiseMatvecTag(),)) + ) + else: + group_data.append( + actx.einsum("ij,ej,ej->ei", + ref_mass_inverse, + jac_inv, + vec_i, + arg_names=("mass_inv_mat", "jac_det_inv", "vec"), + tagged=(HasElementwiseMatvecTag(),)) + ) - return _bound_inverse_mass(dcoll)(u=vec) + return DOFArray(actx, data=tuple(group_data)) -@memoize_on_first_arg -def _bound_face_mass(dcoll, dd): - u = sym.Variable("u", dd=dd) - return bind(dcoll, sym.FaceMassOperator(dd_in=dd)(u), local_only=True) +def inverse_mass(dcoll: DiscretizationCollection, vec): + r"""Return the action of the DG mass matrix inverse on a vector + (or vectors) of :class:`~meshmode.dof_array.DOFArray`\ s, *vec*. + In the case of *vec* being an object array of + :class:`~meshmode.dof_array.DOFArray`\ s, the inverse mass operator is + applied in the Kronecker sense (component-wise). + + For affine elements :math:`E`, the element-wise mass inverse + is computed directly as the inverse of the (physical) mass matrix: + + .. math:: + + \left(\mathbf{M}_{J^e}\right)_{ij} = + \int_{\widehat{E}} \widehat{\phi}_i\cdot\widehat{\phi}_j J^e + \mathrm{d}\widehat{x}, + + where :math:`\widehat{\phi}_i` are basis functions over the reference + element :math:`\widehat{E}`, and :math:`J^e` is the (constant) Jacobian + scaling factor (see :func:`grudge.geometry.area_element`). + + For non-affine :math:`E`, :math:`J^e` is not constant. In this case, a + weight-adjusted approximation is used instead: + + .. math:: + + \mathbf{M}_{J^e}^{-1} \approx + \widehat{\mathbf{M}}^{-1}\mathbf{M}_{1/J^e}\widehat{\mathbf{M}}^{-1}, + + where :math:`\widehat{\mathbf{M}}` is the reference mass matrix on + :math:`\widehat{E}`. + + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + :returns: a :class:`~meshmode.dof_array.DOFArray` denoting the + application of the inverse mass matrix, or an object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + """ + + return _apply_inverse_mass_operator( + dcoll, dof_desc.DD_VOLUME, dof_desc.DD_VOLUME, vec + ) + +# }}} -def face_mass(dcoll, *args): +# {{{ Face mass operator + +def reference_face_mass_matrix( + actx: ArrayContext, face_element_group, vol_element_group, dtype): + @keyed_memoize_in( + actx, reference_mass_matrix, + lambda face_grp, vol_grp: (face_grp.discretization_key(), + vol_grp.discretization_key())) + def get_ref_face_mass_mat(face_grp, vol_grp): + nfaces = vol_grp.mesh_el_group.nfaces + assert face_grp.nelements == nfaces * vol_grp.nelements + + matrix = np.empty( + (vol_grp.nunit_dofs, + nfaces, + face_grp.nunit_dofs), + dtype=dtype + ) + + import modepy as mp + from meshmode.discretization import ElementGroupWithBasis + from meshmode.discretization.poly_element import \ + QuadratureSimplexElementGroup + + n = vol_grp.order + m = face_grp.order + vol_basis = vol_grp.basis_obj() + faces = mp.faces_for_shape(vol_grp.shape) + + for iface, face in enumerate(faces): + # If the face group is defined on a higher-order + # quadrature grid, use the underlying quadrature rule + if isinstance(face_grp, QuadratureSimplexElementGroup): + face_quadrature = face_grp.quadrature_rule() + if face_quadrature.exact_to < m: + raise ValueError( + "The face quadrature rule is only exact for polynomials " + f"of total degree {face_quadrature.exact_to}. Please " + "ensure a quadrature rule is used that is at least " + f"exact for degree {m}." + ) + else: + # NOTE: This handles the general case where + # volume and surface quadrature rules may have different + # integration orders + face_quadrature = mp.quadrature_for_space( + mp.space_for_shape(face, 2*max(n, m)), + face + ) + + # If the group has a nodal basis and is unisolvent, + # we use the basis on the face to compute the face mass matrix + if (isinstance(face_grp, ElementGroupWithBasis) + and face_grp.space.space_dim == face_grp.nunit_dofs): + + face_basis = face_grp.basis_obj() + + # Sanity check for face quadrature accuracy. Not integrating + # degree N + M polynomials here is asking for a bad time. + if face_quadrature.exact_to < m + n: + raise ValueError( + "The face quadrature rule is only exact for polynomials " + f"of total degree {face_quadrature.exact_to}. Please " + "ensure a quadrature rule is used that is at least " + f"exact for degree {n+m}." + ) + + matrix[:, iface, :] = mp.nodal_mass_matrix_for_face( + face, face_quadrature, + face_basis.functions, vol_basis.functions, + vol_grp.unit_nodes, + face_grp.unit_nodes, + ) + else: + # Otherwise, we use a routine that is purely quadrature-based + # (no need for explicit face basis functions) + matrix[:, iface, :] = mp.nodal_quad_mass_matrix_for_face( + face, + face_quadrature, + vol_basis.functions, + vol_grp.unit_nodes, + ) + + return actx.freeze(actx.from_numpy(matrix)) + + return get_ref_face_mass_mat(face_element_group, vol_element_group) + + +def _apply_face_mass_operator(dcoll: DiscretizationCollection, dd, vec): + if isinstance(vec, np.ndarray): + return obj_array_vectorize( + lambda vi: _apply_face_mass_operator(dcoll, dd, vi), vec + ) + + from grudge.geometry import area_element + + volm_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) + face_discr = dcoll.discr_from_dd(dd) + dtype = vec.entry_dtype + actx = vec.array_context + + @memoize_in(actx, (_apply_face_mass_operator, "face_mass_knl")) + def prg(): + return make_loopy_program( + [ + "{[iel]: 0 <= iel < nelements}", + "{[f]: 0 <= f < nfaces}", + "{[idof]: 0 <= idof < nvol_nodes}", + "{[jdof]: 0 <= jdof < nface_nodes}" + ], + """ + result[iel, idof] = sum(f, sum(jdof, mat[idof, f, jdof] + * jac_surf[f, iel, jdof] + * vec[f, iel, jdof])) + """, + name="face_mass" + ) + + assert len(face_discr.groups) == len(volm_discr.groups) + surf_area_elements = area_element(actx, dcoll, dd=dd) + + return DOFArray( + actx, + data=tuple( + actx.call_loopy(prg(), + mat=reference_face_mass_matrix( + actx, + face_element_group=afgrp, + vol_element_group=vgrp, + dtype=dtype + ), + jac_surf=surf_ae_i.reshape( + vgrp.mesh_el_group.nfaces, + vgrp.nelements, + afgrp.nunit_dofs + ), + vec=vec_i.reshape( + vgrp.mesh_el_group.nfaces, + vgrp.nelements, + afgrp.nunit_dofs + ))["result"] + + for vgrp, afgrp, vec_i, surf_ae_i in zip(volm_discr.groups, + face_discr.groups, + vec, + surf_area_elements) + ) + ) + + +def face_mass(dcoll: DiscretizationCollection, *args): + r"""Return the action of the DG face mass matrix on a vector (or vectors) + of :class:`~meshmode.dof_array.DOFArray`\ s, *vec*. In the case of + *vec* being an object array of :class:`~meshmode.dof_array.DOFArray`\ s, + the mass operator is applied in the Kronecker sense (component-wise). + + May be called with ``(vec)`` or ``(dd, vec)``. + + Specifically, this function applies the face mass matrix elementwise on a + vector of coefficients :math:`\mathbf{f}` as the sum of contributions for + each face :math:`f \subset \partial E`: + + .. math:: + + \sum_{f=1}^{N_{\text{faces}}} \mathbf{M}_{f, E}\mathbf{f}|_f, + + where + + .. math:: + + \left(\mathbf{M}_{f, E}\right)_{ij} = + \int_{f \subset \partial E} \phi_i(s)\psi_j(s)\,\mathrm{d}s, + + where :math:`\phi_i` are (volume) polynomial basis functions on :math:`E` + evaluated on the face :math:`f`, and :math:`\psi_j` are basis functions for + a polynomial space defined on :math:`f`. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base ``"all_faces"`` discretization if not provided. + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + :returns: a :class:`~meshmode.dof_array.DOFArray` denoting the + application of the face mass matrix, or an object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + """ + if len(args) == 1: vec, = args dd = dof_desc.DOFDesc("all_faces", dof_desc.DISCR_TAG_BASE) @@ -367,27 +1040,49 @@ def face_mass(dcoll, *args): else: raise TypeError("invalid number of arguments") - if isinstance(vec, np.ndarray): - return obj_array_vectorize( - lambda el: face_mass(dcoll, dd, el), vec) - - return _bound_face_mass(dcoll, dd)(u=vec) + return _apply_face_mass_operator(dcoll, dd, vec) # }}} -# {{{ reductions +# {{{ Nodal reductions -@memoize_on_first_arg -def _norm(dcoll, p, dd): - return bind(dcoll, - sym.norm(p, sym.var("arg", dd=dd), dd=dd), - local_only=True) +def _norm(dcoll: DiscretizationCollection, vec, p, dd): + if isinstance(vec, Number): + return np.fabs(vec) + if p == 2: + return np.sqrt( + nodal_sum( + dcoll, + dd, + vec * _apply_mass_operator(dcoll, dd, dd, vec) + ) + ) + elif p == np.inf: + return nodal_max(dcoll, dd, dcoll._setup_actx.np.fabs(vec)) + else: + raise NotImplementedError("Unsupported value of p") -def norm(dcoll, vec, p, dd=None): +def norm(dcoll: DiscretizationCollection, vec, p, dd=None): + r"""Return the vector p-norm of a function represented + by its vector of degrees of freedom *vec*. + + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or an object array of + a :class:`~meshmode.dof_array.DOFArray`\ s, + where the last axis of the array must have length + matching the volume dimension. + :arg p: an integer denoting the order of the integral norm. Currently, + only values of 2 or `numpy.inf` are supported. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization if not provided. + :returns: a nonegative scalar denoting the norm. + """ + # FIXME: Make MPI-aware + # NOTE: Must retain a way to do local reductions + if dd is None: - dd = "vol" + dd = dof_desc.DD_VOLUME dd = dof_desc.as_dofdesc(dd) @@ -403,155 +1098,132 @@ def norm(dcoll, vec, p, dd=None): else: raise ValueError("unsupported norm order") - return _norm(dcoll, p, dd)(arg=vec) - + return _norm(dcoll, vec, p, dd) -@memoize_on_first_arg -def _nodal_reduction(dcoll, operator, dd): - return bind(dcoll, operator(dd)(sym.var("arg")), local_only=True) +def nodal_sum(dcoll: DiscretizationCollection, dd, vec): + r"""Return the nodal sum of a vector of degrees of freedom *vec*. -def nodal_sum(dcoll, dd, vec): - return _nodal_reduction(dcoll, sym.NodalSum, dd)(arg=vec) + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. + :arg vec: a :class:`~meshmode.dof_array.DOFArray`. + :returns: a scalar denoting the nodal sum. + """ + # FIXME: Make MPI-aware + # NOTE: Must retain a way to do local reductions + actx = vec.array_context + return sum([actx.np.sum(grp_ary) for grp_ary in vec]) -def nodal_min(dcoll, dd, vec): - return _nodal_reduction(dcoll, sym.NodalMin, dd)(arg=vec) +def nodal_min(dcoll: DiscretizationCollection, dd, vec): + r"""Return the nodal minimum of a vector of degrees of freedom *vec*. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. + :arg vec: a :class:`~meshmode.dof_array.DOFArray`. + :returns: a scalar denoting the nodal minimum. + """ + # FIXME: Make MPI-aware + # NOTE: Must retain a way to do local reductions + actx = vec.array_context + return reduce(lambda acc, grp_ary: actx.np.minimum(acc, actx.np.min(grp_ary)), + vec, -np.inf) -def nodal_max(dcoll, dd, vec): - return _nodal_reduction(dcoll, sym.NodalMax, dd)(arg=vec) -# }}} +def nodal_max(dcoll: DiscretizationCollection, dd, vec): + r"""Return the nodal maximum of a vector of degrees of freedom *vec*. - -@memoize_on_first_arg -def connected_ranks(dcoll): - from meshmode.distributed import get_connected_partitions - return get_connected_partitions(dcoll._volume_discr.mesh) + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value + convertible to one. + :arg vec: a :class:`~meshmode.dof_array.DOFArray`. + :returns: a scalar denoting the nodal maximum. + """ + # FIXME: Make MPI-aware + # NOTE: Must retain a way to do local reductions + actx = vec.array_context + return reduce(lambda acc, grp_ary: actx.np.maximum(acc, actx.np.max(grp_ary)), + vec, -np.inf) -# {{{ interior_trace_pair +def integral(dcoll: DiscretizationCollection, dd, vec): + """Numerically integrates a function represented by a + :class:`~meshmode.dof_array.DOFArray` of degrees of freedom. -def interior_trace_pair(dcoll, vec): - """Return a :class:`grudge.sym.TracePair` for the interior faces of - *dcoll*. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + :arg vec: a :class:`~meshmode.dof_array.DOFArray` + :returns: a scalar denoting the evaluated integral. """ - i = project(dcoll, "vol", "int_faces", vec) - - def get_opposite_face(el): - if isinstance(el, Number): - return el - else: - return dcoll.opposite_face_connection()(el) - e = obj_array_vectorize(get_opposite_face, i) + dd = dof_desc.as_dofdesc(dd) - return TracePair("int_faces", interior=i, exterior=e) + ones = dcoll.discr_from_dd(dd).zeros(vec.array_context) + 1.0 + return nodal_sum( + dcoll, dd, vec * _apply_mass_operator(dcoll, dd, dd, ones) + ) # }}} -# {{{ distributed-memory functionality - -class _RankBoundaryCommunication: - base_tag = 1273 - - def __init__(self, dcoll, remote_rank, vol_field, tag=None): - self.tag = self.base_tag - if tag is not None: - self.tag += tag - - self.dcoll = dcoll - self.array_context = vol_field.array_context - self.remote_btag = BTAG_PARTITION(remote_rank) - - self.bdry_discr = dcoll.discr_from_dd(self.remote_btag) - self.local_dof_array = project(dcoll, "vol", self.remote_btag, vol_field) +# {{{ Elementwise reductions - local_data = self.array_context.to_numpy(flatten(self.local_dof_array)) +def _map_elementwise_reduction(actx: ArrayContext, op_name): + @memoize_in(actx, (_map_elementwise_reduction, + "elementwise_%s_prg" % op_name)) + def prg(): + return make_loopy_program( + [ + "{[iel]: 0 <= iel < nelements}", + "{[idof, jdof]: 0 <= idof, jdof < ndofs}" + ], + """ + result[iel, idof] = %s(jdof, operand[iel, jdof]) + """ % op_name, + name="grudge_elementwise_%s_knl" % op_name + ) + return prg() - comm = self.dcoll.mpi_communicator - self.send_req = comm.Isend( - local_data, remote_rank, tag=self.tag) +def elementwise_sum(dcoll: DiscretizationCollection, *args): + r"""Returns a vector of DOFs with all entries on each element set + to the sum of DOFs on that element. - self.remote_data_host = np.empty_like(local_data) - self.recv_req = comm.Irecv(self.remote_data_host, remote_rank, self.tag) - - def finish(self): - self.recv_req.Wait() - - actx = self.array_context - remote_dof_array = unflatten(self.array_context, self.bdry_discr, - actx.from_numpy(self.remote_data_host)) - - bdry_conn = self.dcoll.get_distributed_boundary_swap_connection( - dof_desc.as_dofdesc(dof_desc.DTAG_BOUNDARY(self.remote_btag))) - swapped_remote_dof_array = bdry_conn(remote_dof_array) - - self.send_req.Wait() - - return TracePair(self.remote_btag, - interior=self.local_dof_array, - exterior=swapped_remote_dof_array) + May be called with ``(dcoll, vec)`` or ``(dcoll, dd, vec)``. + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. + Defaults to the base volume discretization if not provided. + :arg vec: a :class:`~meshmode.dof_array.DOFArray` + :returns: a :class:`~meshmode.dof_array.DOFArray` whose entries + denote the element-wise sum of *vec*. + """ -def _cross_rank_trace_pairs_scalar_field(dcoll, vec, tag=None): - if isinstance(vec, Number): - return [TracePair(BTAG_PARTITION(remote_rank), interior=vec, exterior=vec) - for remote_rank in connected_ranks(dcoll)] + if len(args) == 1: + vec, = args + dd = dof_desc.DOFDesc("vol", dof_desc.DISCR_TAG_BASE) + elif len(args) == 2: + dd, vec = args else: - rbcomms = [_RankBoundaryCommunication(dcoll, remote_rank, vec, tag=tag) - for remote_rank in connected_ranks(dcoll)] - return [rbcomm.finish() for rbcomm in rbcomms] - - -def cross_rank_trace_pairs(dcoll, ary, tag=None): - r"""Get a list of *ary* trace pairs for each partition boundary. - - For each partition boundary, the field data values in *ary* are - communicated to/from the neighboring partition. Presumably, this - communication is MPI (but strictly speaking, may not be, and this - routine is agnostic to the underlying communication, see e.g. - _cross_rank_trace_pairs_scalar_field). + raise TypeError("invalid number of arguments") - For each face on each partition boundary, a :class:`TracePair` is - created with the locally, and remotely owned partition boundary face - data as the `internal`, and `external` components, respectively. - Each of the TracePair components are structured like *ary*. + dd = dof_desc.as_dofdesc(dd) - The input field data *ary* may be a single - :class:`~meshmode.dof_array.DOFArray`, or an object - array of ``DOFArray``\ s of arbitrary shape. - """ - if isinstance(ary, np.ndarray): - oshape = ary.shape - comm_vec = ary.flatten() - - n, = comm_vec.shape - result = {} - # FIXME: Batch this communication rather than - # doing it in sequence. - for ivec in range(n): - for rank_tpair in _cross_rank_trace_pairs_scalar_field( - dcoll, comm_vec[ivec]): - assert isinstance(rank_tpair.dd.domain_tag, dof_desc.DTAG_BOUNDARY) - assert isinstance(rank_tpair.dd.domain_tag.tag, BTAG_PARTITION) - result[rank_tpair.dd.domain_tag.tag.part_nr, ivec] = rank_tpair - - return [ - TracePair( - dd=dof_desc.as_dofdesc( - dof_desc.DTAG_BOUNDARY(BTAG_PARTITION(remote_rank))), - interior=make_obj_array([ - result[remote_rank, i].int for i in range(n)]).reshape(oshape), - exterior=make_obj_array([ - result[remote_rank, i].ext for i in range(n)]).reshape(oshape) - ) - for remote_rank in connected_ranks(dcoll)] - else: - return _cross_rank_trace_pairs_scalar_field(dcoll, ary, tag=tag) + if isinstance(vec, np.ndarray): + return obj_array_vectorize( + lambda vi: elementwise_sum(dcoll, dd, vi), vec + ) + + actx = vec.array_context + vec = project(dcoll, "vol", dd, vec) + + return DOFArray( + actx, + data=tuple( + actx.call_loopy( + _map_elementwise_reduction(actx, "sum"), + operand=vec_i + )["result"] + for vec_i in vec + ) + ) # }}} diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py index c880d9d0925d0b8c48a01b788cf81249160865e9..d95bc657b4f7f1c848f9eb116f4e4fcb2b1ed1c2 100644 --- a/grudge/symbolic/primitives.py +++ b/grudge/symbolic/primitives.py @@ -34,6 +34,7 @@ from pymbolic.primitives import ( cse_scope as cse_scope_base, make_common_subexpression as cse) from pymbolic.geometric_algebra import MultiVector +from grudge.trace_pair import TracePair class ExpressionBase(prim.Expression): @@ -94,10 +95,9 @@ Geometry data .. autofunction:: summed_curvature .. autofunction:: mean_curvature -Trace Pair -^^^^^^^^^^ +Symbolic trace pair functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: TracePair .. autofunction:: int_tpair .. autofunction:: bv_tpair .. autofunction:: bdry_tpair @@ -668,64 +668,7 @@ def mean_curvature(ambient_dim, dim=None, dd=None): # }}} -# {{{ trace pair - -class TracePair: - """ - .. attribute:: dd - - an instance of :class:`grudge.dof_desc.DOFDesc` describing the - discretization on which :attr:`interior` and :attr:`exterior` - live. - - .. attribute:: interior - - a value (symbolic expression or :class:`~meshmode.dof_array.DOFArray` - or object array of either) representing the interior value to - be used for the flux. - - .. attribute:: exterior - - a value (symbolic expression or :class:`~meshmode.dof_array.DOFArray` - or object array of either) representing the exterior value to - be used for the flux. - - .. note:: - - :class:`TracePair` is used both by the symbolic and the eager interface, - with symbolic information or concrete data. - """ - def __init__(self, dd, *, interior, exterior): - """ - """ - import grudge.dof_desc as dof_desc - - self.dd = dof_desc.as_dofdesc(dd) - self.interior = interior - self.exterior = exterior - - def __getitem__(self, index): - return TracePair( - self.dd, - interior=self.interior[index], - exterior=self.exterior[index]) - - def __len__(self): - assert len(self.exterior) == len(self.interior) - return len(self.exterior) - - @property - def int(self): - return self.interior - - @property - def ext(self): - return self.exterior - - @property - def avg(self): - return 0.5*(self.int + self.ext) - +# {{{ Symbolic trace pair functions def int_tpair(expression, qtag=None, from_dd=None): from meshmode.discretization.connection import FACE_RESTR_INTERIOR diff --git a/grudge/trace_pair.py b/grudge/trace_pair.py new file mode 100644 index 0000000000000000000000000000000000000000..ad2e9c815f6b38d48f7dabf95314bdfceda553fc --- /dev/null +++ b/grudge/trace_pair.py @@ -0,0 +1,375 @@ +__copyright__ = """ +Copyright (C) 2021 University of Illinois Board of Trustees +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +from arraycontext import ( + ArrayContainer, + with_container_arithmetic, + dataclass_array_container +) + +from dataclasses import dataclass + +from numbers import Number + +from pytools import memoize_on_first_arg +from pytools.obj_array import obj_array_vectorize, make_obj_array + +from grudge.discretization import DiscretizationCollection + +from meshmode.dof_array import flatten, unflatten +from meshmode.mesh import BTAG_PARTITION + +import numpy as np +import grudge.dof_desc as dof_desc + + +__doc__ = """ +Trace Pairs +^^^^^^^^^^^ + +Container class +--------------- + +.. autoclass:: TracePair + +Boundary trace functions +------------------------ + +.. autofunction:: bdry_trace_pair +.. autofunction:: bv_trace_pair + +Interior and cross-rank trace functions +--------------------------------------- + +.. autofunction:: interior_trace_pairs +.. autofunction:: cross_rank_trace_pairs +""" + + +# {{{ Trace pair container class + +@with_container_arithmetic( + bcast_obj_array=False, eq_comparison=False, rel_comparison=False +) +@dataclass_array_container +@dataclass(init=False, frozen=True) +class TracePair: + """A container class for data (both interior and exterior restrictions) + on the boundaries of mesh elements. + + .. attribute:: dd + + an instance of :class:`grudge.dof_desc.DOFDesc` describing the + discretization on which :attr:`int` and :attr:`ext` live. + + .. autoattribute:: int + .. autoattribute:: ext + .. autoattribute:: avg + + .. automethod:: __getattr__ + .. automethod:: __getitem__ + .. automethod:: __len__ + + .. note:: + + :class:`TracePair` is currently used both by the symbolic (deprecated) + and the current interfaces, with symbolic information or concrete data. + """ + + dd: dof_desc.DOFDesc + interior: ArrayContainer + exterior: ArrayContainer + + def __init__(self, dd, *, interior, exterior): + object.__setattr__(self, "dd", dof_desc.as_dofdesc(dd)) + object.__setattr__(self, "interior", interior) + object.__setattr__(self, "exterior", exterior) + + def __getattr__(self, name): + """Return a new :class:`TracePair` resulting from executing attribute + lookup with *name* on :attr:`int` and :attr:`ext`. + """ + return TracePair(self.dd, + interior=getattr(self.interior, name), + exterior=getattr(self.exterior, name)) + + def __getitem__(self, index): + """Return a new :class:`TracePair` resulting from executing + subscripting with *index* on :attr:`int` and :attr:`ext`. + """ + return TracePair(self.dd, + interior=self.interior[index], + exterior=self.exterior[index]) + + def __len__(self): + """Return the total number of arrays associated with the + :attr:`int` and :attr:`ext` restrictions of the :class:`TracePair`. + Note that both must be the same. + """ + assert len(self.exterior) == len(self.interior) + return len(self.exterior) + + @property + def int(self): + """A value (symbolic expression or :class:`~meshmode.dof_array.DOFArray` + or object array of either) representing the interior value to + be used for the flux. + """ + return self.interior + + @property + def ext(self): + """A value (symbolic expression or :class:`~meshmode.dof_array.DOFArray` + or object array of either) representing the exterior value to + be used for the flux. + """ + return self.exterior + + @property + def avg(self): + """A value (symbolic expression or :class:`~meshmode.dof_array.DOFArray` + or object array of either) representing the average of the interior + and exterior values. + """ + return 0.5 * (self.int + self.ext) + +# }}} + + +# {{{ Boundary trace pairs + +def bdry_trace_pair( + dcoll: DiscretizationCollection, dd, interior, exterior) -> TracePair: + """Returns a trace pair defined on the exterior boundary. Input arguments + are assumed to already be defined on the boundary denoted by *dd*. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one, + which describes the boundary discretization. + :arg interior: a :class:`~meshmode.dof_array.DOFArray` that contains data + already on the boundary representing the interior value to be used + for the flux. + :arg exterior: a :class:`~meshmode.dof_array.DOFArray` that contains data + that already lives on the boundary representing the exterior value to + be used for the flux. + :returns: a :class:`TracePair` on the boundary. + """ + return TracePair(dd, interior=interior, exterior=exterior) + + +def bv_trace_pair( + dcoll: DiscretizationCollection, dd, interior, exterior) -> TracePair: + """Returns a trace pair defined on the exterior boundary. The interior + argument is assumed to be defined on the volume discretization, and will + therefore be restricted to the boundary *dd* prior to creating a + :class:`TracePair`. + + :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one, + which describes the boundary discretization. + :arg interior: a :class:`~meshmode.dof_array.DOFArray` that contains data + defined in the volume, which will be restricted to the boundary denoted + by *dd*. The result will be used as the interior value + for the flux. + :arg exterior: a :class:`~meshmode.dof_array.DOFArray` that contains data + that already lives on the boundary representing the exterior value to + be used for the flux. + :returns: a :class:`TracePair` on the boundary. + """ + from grudge.op import project + + interior = project(dcoll, "vol", dd, interior) + return bdry_trace_pair(dcoll, dd, interior, exterior) + +# }}} + + +# {{{ Interior trace pairs + +def _interior_trace_pair(dcoll: DiscretizationCollection, vec) -> TracePair: + r"""Return a :class:`TracePair` for the interior faces of + *dcoll* with a discretization tag specified by *discr_tag*. + This does not include interior faces on different MPI ranks. + + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + :returns: a :class:`TracePair` object. + """ + from grudge.op import project + + i = project(dcoll, "vol", "int_faces", vec) + + def get_opposite_face(el): + if isinstance(el, Number): + return el + else: + return dcoll.opposite_face_connection()(el) + + e = obj_array_vectorize(get_opposite_face, i) + + return TracePair("int_faces", interior=i, exterior=e) + + +def interior_trace_pairs(dcoll: DiscretizationCollection, vec) -> list: + r"""Return a :class:`list` of :class:`TracePair` objects + defined on the interior faces of *dcoll* and any faces connected to a + parallel boundary. + + :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of + :class:`~meshmode.dof_array.DOFArray`\ s. + :returns: a :class:`list` of :class:`TracePair` objects. + """ + return ( + [_interior_trace_pair(dcoll, vec)] + + cross_rank_trace_pairs(dcoll, vec) + ) + + +def interior_trace_pair(dcoll: DiscretizationCollection, vec) -> TracePair: + from warnings import warn + warn("`grudge.op.interior_trace_pair` is deprecated and will be dropped " + "in version 2022.x. Use `grudge.trace_pair.interior_trace_pairs` " + "instead, which includes contributions from different MPI ranks.", + DeprecationWarning, stacklevel=2) + return _interior_trace_pair(dcoll, vec) + +# }}} + + +# {{{ Distributed-memory functionality + +@memoize_on_first_arg +def connected_ranks(dcoll: DiscretizationCollection): + from meshmode.distributed import get_connected_partitions + return get_connected_partitions(dcoll._volume_discr.mesh) + + +class _RankBoundaryCommunication: + base_tag = 1273 + + def __init__(self, dcoll: DiscretizationCollection, + remote_rank, vol_field, tag=None): + self.tag = self.base_tag + if tag is not None: + self.tag += tag + + self.dcoll = dcoll + self.array_context = vol_field.array_context + self.remote_btag = BTAG_PARTITION(remote_rank) + self.bdry_discr = dcoll.discr_from_dd(self.remote_btag) + + from grudge.op import project + + self.local_dof_array = project(dcoll, "vol", self.remote_btag, vol_field) + + local_data = self.array_context.to_numpy(flatten(self.local_dof_array)) + comm = self.dcoll.mpi_communicator + + self.send_req = comm.Isend(local_data, remote_rank, tag=self.tag) + self.remote_data_host = np.empty_like(local_data) + self.recv_req = comm.Irecv(self.remote_data_host, remote_rank, self.tag) + + def finish(self): + self.recv_req.Wait() + + actx = self.array_context + remote_dof_array = unflatten( + self.array_context, self.bdry_discr, + actx.from_numpy(self.remote_data_host) + ) + + bdry_conn = self.dcoll.distributed_boundary_swap_connection( + dof_desc.as_dofdesc(dof_desc.DTAG_BOUNDARY(self.remote_btag)) + ) + swapped_remote_dof_array = bdry_conn(remote_dof_array) + + self.send_req.Wait() + + return TracePair(self.remote_btag, + interior=self.local_dof_array, + exterior=swapped_remote_dof_array) + + +def _cross_rank_trace_pairs_scalar_field( + dcoll: DiscretizationCollection, vec, tag=None) -> list: + if isinstance(vec, Number): + return [TracePair(BTAG_PARTITION(remote_rank), interior=vec, exterior=vec) + for remote_rank in connected_ranks(dcoll)] + else: + rbcomms = [_RankBoundaryCommunication(dcoll, remote_rank, vec, tag=tag) + for remote_rank in connected_ranks(dcoll)] + return [rbcomm.finish() for rbcomm in rbcomms] + + +def cross_rank_trace_pairs( + dcoll: DiscretizationCollection, ary, tag=None) -> list: + r"""Get a :class:`list` of *ary* trace pairs for each partition boundary. + + For each partition boundary, the field data values in *ary* are + communicated to/from the neighboring partition. Presumably, this + communication is MPI (but strictly speaking, may not be, and this + routine is agnostic to the underlying communication). + + For each face on each partition boundary, a + :class:`TracePair` is created with the locally, and + remotely owned partition boundary face data as the `internal`, and `external` + components, respectively. Each of the TracePair components are structured + like *ary*. + + :arg ary: a single :class:`~meshmode.dof_array.DOFArray`, or an object + array of :class:`~meshmode.dof_array.DOFArray`\ s + of arbitrary shape. + :returns: a :class:`list` of :class:`TracePair` objects. + """ + if isinstance(ary, np.ndarray): + oshape = ary.shape + comm_vec = ary.flatten() + + n, = comm_vec.shape + result = {} + # FIXME: Batch this communication rather than + # doing it in sequence. + for ivec in range(n): + for rank_tpair in _cross_rank_trace_pairs_scalar_field( + dcoll, comm_vec[ivec]): + assert isinstance(rank_tpair.dd.domain_tag, dof_desc.DTAG_BOUNDARY) + assert isinstance(rank_tpair.dd.domain_tag.tag, BTAG_PARTITION) + result[rank_tpair.dd.domain_tag.tag.part_nr, ivec] = rank_tpair + + return [ + TracePair( + dd=dof_desc.as_dofdesc( + dof_desc.DTAG_BOUNDARY(BTAG_PARTITION(remote_rank))), + interior=make_obj_array([ + result[remote_rank, i].int for i in range(n)]).reshape(oshape), + exterior=make_obj_array([ + result[remote_rank, i].ext for i in range(n)]).reshape(oshape) + ) for remote_rank in connected_ranks(dcoll) + ] + else: + return _cross_rank_trace_pairs_scalar_field(dcoll, ary, tag=tag) + +# }}} + + +# vim: foldmethod=marker diff --git a/setup.cfg b/setup.cfg index 38dcd9de2c01e21191ca1612388f36ecbc251b70..9d6debd0c7358440a314c9f4f17ced3e4fb260e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,11 +2,6 @@ ignore = E126,E127,E128,E123,E226,E241,E242,E265,W503,E402 max-line-length=85 exclude= - grudge/models/gas_dynamics, - grudge/models/burgers.py, - grudge/models/pml.py, - grudge/models/diffusion.py, - grudge/models/nd_calculus.py, grudge/dt_finding.py inline-quotes = " diff --git a/test/mesh_data.py b/test/mesh_data.py index 5529856eb5f01f135d8f546ceca7750adced02af..b6effe4c0d303f3d0b0f35286bf0e21ef5a926aa 100644 --- a/test/mesh_data.py +++ b/test/mesh_data.py @@ -113,7 +113,7 @@ class BoxMeshBuilder(MeshBuilder): ambient_dim = 2 mesh_order = 1 - resolutions = [8, 16, 32] + resolutions = [4, 8, 16] a = (-0.5, -0.5, -0.5) b = (+0.5, +0.5, +0.5) diff --git a/test/test_grudge.py b/test/test_grudge.py index f246a895c2bf2b1229986c52c152d405aebf68b4..41a52edf22e30f4121350ed3575c825ebc5018ae 100644 --- a/test/test_grudge.py +++ b/test/test_grudge.py @@ -1,4 +1,7 @@ -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,20 +26,25 @@ THE SOFTWARE. import numpy as np import numpy.linalg as la +from arraycontext import ( # noqa + pytest_generate_tests_for_pyopencl_array_context + as pytest_generate_tests +) +from arraycontext.container.traversal import thaw + from meshmode import _acf # noqa: F401 -from meshmode.dof_array import flatten, thaw +from meshmode.dof_array import flat_norm import meshmode.mesh.generation as mgen from pytools.obj_array import flat_obj_array, make_obj_array -from grudge import sym, bind, DiscretizationCollection +from grudge import DiscretizationCollection import grudge.dof_desc as dof_desc +import grudge.op as op + import pytest -from meshmode.array_context import ( # noqa - pytest_generate_tests_for_pyopencl_array_context - as pytest_generate_tests) import logging @@ -67,23 +75,19 @@ def test_inverse_metric(actx_factory, dim): from meshmode.mesh.processing import map_mesh mesh = map_mesh(mesh, m) - discr = DiscretizationCollection(actx, mesh, order=4) + dcoll = DiscretizationCollection(actx, mesh, order=4) - sym_op = ( - sym.forward_metric_derivative_mat(mesh.dim) - .dot( - sym.inverse_metric_derivative_mat(mesh.dim) - ) - .reshape(-1)) + from grudge.geometry import \ + forward_metric_derivative_mat, inverse_metric_derivative_mat - op = bind(discr, sym_op) - mat = op(actx).reshape(mesh.dim, mesh.dim) + 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 = actx.np.linalg.norm(mat[i, j] - tgt, ord=np.inf) + 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) @@ -120,48 +124,49 @@ def test_mass_mat_trig(actx_factory, ambient_dim, discr_tag): mesh = mgen.generate_regular_rect_mesh( a=(a,)*ambient_dim, b=(b,)*ambient_dim, nelements_per_axis=(nel_1d,)*ambient_dim, order=1) - discr = DiscretizationCollection( + dcoll = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory ) - def _get_variables_on(dd): - sym_f = sym.var("f", dd=dd) - sym_x = sym.nodes(ambient_dim, dd=dd) - sym_ones = sym.Ones(dd) - - return sym_f, sym_x, sym_ones + def f(x): + return actx.np.sin(x[0])**2 - sym_f, sym_x, sym_ones = _get_variables_on(dof_desc.DD_VOLUME) - f_volm = actx.to_numpy(flatten(bind(discr, sym.cos(sym_x[0])**2)(actx))) - ones_volm = actx.to_numpy(flatten(bind(discr, sym_ones)(actx))) + volm_disc = dcoll.discr_from_dd(dof_desc.DD_VOLUME) + x_volm = thaw(volm_disc.nodes(), actx) + f_volm = f(x_volm) + ones_volm = volm_disc.zeros(actx) + 1 - sym_f, sym_x, sym_ones = _get_variables_on(dd_quad) - f_quad = bind(discr, sym.cos(sym_x[0])**2)(actx) - ones_quad = bind(discr, sym_ones)(actx) + quad_disc = dcoll.discr_from_dd(dd_quad) + x_quad = thaw(quad_disc.nodes(), actx) + f_quad = f(x_quad) + ones_quad = quad_disc.zeros(actx) + 1 - mass_op = bind(discr, sym.MassOperator(dd_quad, dof_desc.DD_VOLUME)(sym_f)) + mop_1 = op.mass(dcoll, dd_quad, f_quad) + num_integral_1 = op.nodal_sum( + dcoll, dof_desc.DD_VOLUME, ones_volm * mop_1 + ) - num_integral_1 = np.dot(ones_volm, actx.to_numpy(flatten(mass_op(f=f_quad)))) err_1 = abs(num_integral_1 - true_integral) - assert err_1 < 1e-9, err_1 + assert err_1 < 2e-9, err_1 + + mop_2 = op.mass(dcoll, dd_quad, ones_quad) + num_integral_2 = op.nodal_sum(dcoll, dof_desc.DD_VOLUME, f_volm * mop_2) - num_integral_2 = np.dot(f_volm, actx.to_numpy(flatten(mass_op(f=ones_quad)))) err_2 = abs(num_integral_2 - true_integral) - assert err_2 < 1.0e-9, err_2 + assert err_2 < 2e-9, err_2 if discr_tag is dof_desc.DISCR_TAG_BASE: # NOTE: `integral` always makes a square mass matrix and # `QuadratureSimplexGroupFactory` does not have a `mass_matrix` method. - num_integral_3 = bind(discr, - sym.integral(sym_f, dd=dd_quad))(f=f_quad) + num_integral_3 = op.nodal_sum(dcoll, dof_desc.DD_VOLUME, f_quad * mop_2) err_3 = abs(num_integral_3 - true_integral) - assert err_3 < 5.0e-10, err_3 + assert err_3 < 5e-10, err_3 # }}} -# {{{ mass operator surface area +# {{{ mass operator on surface def _ellipse_surface_area(radius, aspect_ratio): # https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.ellipe.html @@ -226,8 +231,8 @@ def test_mass_surface_area(actx_factory, name): 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) + 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) @@ -235,8 +240,8 @@ def test_mass_surface_area(actx_factory, name): # {{{ 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) + ones_volm = volume_discr.zeros(actx) + 1 + approx_surface_area = op.integral(dcoll, dd, ones_volm) logger.info("surface: got {:.5e} / expected {:.5e}".format( approx_surface_area, surface_area)) @@ -244,21 +249,21 @@ def test_mass_surface_area(actx_factory, name): # }}} - 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) + # compute max element size + h_max = op.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() < 1.0e-14 \ - or eoc.order_estimate() > builder.order + assert eoc.max_error() < 3e-13 or eoc.order_estimate() > builder.order # }}} -# {{{ surface mass inverse +# {{{ mass inverse on surfaces @pytest.mark.parametrize("name", ["2-1-ellipse", "spheroid"]) def test_surface_mass_operator_inverse(actx_factory, name): @@ -284,30 +289,31 @@ def test_surface_mass_operator_inverse(actx_factory, name): 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) + 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 - 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"))) + def f(x): + return actx.np.cos(4.0 * x[0]) - f = bind(discr, sym_f)(actx) - f_inv = bind(discr, sym_op)(actx, f=f) + 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 = bind(discr, - sym.norm(2, sym.var("x") - sym.var("y")) - / sym.norm(2, sym.var("y")))(actx, x=f_inv, y=f) + inv_error = op.norm(dcoll, f_volm - f_inv, 2) / op.norm(dcoll, f_volm, 2) # }}} - h_max = bind(discr, sym.h_max_from_volume( - discr.ambient_dim, dim=discr.dim, dd=dd))(actx) + # compute max element size + h_max = op.h_max_from_volume(dcoll) + eoc.add_data_point(h_max, inv_error) # }}} @@ -340,51 +346,48 @@ def test_face_normal_surface(actx_factory, mesh_name): raise ValueError("unknown mesh name: %s" % mesh_name) mesh = builder.get_mesh(builder.resolutions[0], builder.mesh_order) - discr = DiscretizationCollection(actx, mesh, order=builder.order) + dcoll = DiscretizationCollection(actx, mesh, order=builder.order) - volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) + 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) # }}} - # {{{ symbolic + # {{{ Compute surface and face normals from meshmode.discretization.connection import FACE_RESTR_INTERIOR + from grudge.geometry import normal dv = dof_desc.DD_VOLUME df = dof_desc.as_dofdesc(FACE_RESTR_INTERIOR) ambient_dim = mesh.ambient_dim - dim = mesh.dim - sym_surf_normal = sym.project(dv, df)( - sym.surface_normal(ambient_dim, dim=dim, dd=dv).as_vector() - ) - sym_surf_normal = sym_surf_normal / sym.sqrt(sum(sym_surf_normal**2)) + surf_normal = op.project( + dcoll, dv, df, + normal(actx, dcoll, dd=dv) + ) + surf_normal = surf_normal / actx.np.sqrt(sum(surf_normal**2)) - sym_face_normal_i = sym.normal(df, ambient_dim, dim=dim - 1) - sym_face_normal_e = sym.OppositeInteriorFaceSwap(df)(sym_face_normal_i) + face_normal_i = thaw(op.normal(dcoll, df), actx) + face_normal_e = dcoll.opposite_face_connection()(face_normal_i) if mesh.ambient_dim == 3: + from grudge.geometry import pseudoscalar, area_element # NOTE: there's only one face tangent in 3d - sym_face_tangent = ( - sym.pseudoscalar(ambient_dim, dim - 1, dd=df) - / sym.area_element(ambient_dim, dim - 1, dd=df)).as_vector() + face_tangent = ( + pseudoscalar(actx, dcoll, dd=df) / area_element(actx, dcoll, dd=df) + ).as_vector(dtype=object) # }}} # {{{ checks def _eval_error(x): - return bind(discr, sym.norm(np.inf, sym.var("x", dd=df), dd=df))(actx, x=x) + return op.norm(dcoll, x, np.inf, dd=df) rtol = 1.0e-14 - surf_normal = bind(discr, sym_surf_normal)(actx) - - face_normal_i = bind(discr, sym_face_normal_i)(actx) - face_normal_e = bind(discr, sym_face_normal_e)(actx) - # check interpolated surface normal is orthogonal to face normal error = _eval_error(surf_normal.dot(face_normal_i)) logger.info("error[n_dot_i]: %.5e", error) @@ -397,8 +400,6 @@ def test_face_normal_surface(actx_factory, mesh_name): # check orthogonality with face tangent if ambient_dim == 3: - face_tangent = bind(discr, sym_face_tangent)(actx) - error = _eval_error(face_tangent.dot(face_normal_i)) logger.info("error[t_dot_i]: %.5e", error) assert error < 5 * rtol @@ -422,24 +423,25 @@ def test_tri_diff_mat(actx_factory, dim, order=4): from pytools.convergence import EOCRecorder axis_eoc_recs = [EOCRecorder() for axis in range(dim)] + def f(x, axis): + return actx.np.sin(3*x[axis]) + + def df(x, axis): + return 3*actx.np.cos(3*x[axis]) + for n in [4, 8, 16]: mesh = mgen.generate_regular_rect_mesh(a=(-0.5,)*dim, b=(0.5,)*dim, nelements_per_axis=(n,)*dim, order=4) - discr = DiscretizationCollection(actx, mesh, order=4) - nabla = sym.nabla(dim) + dcoll = DiscretizationCollection(actx, mesh, order=4) + volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) + x = thaw(volume_discr.nodes(), actx) for axis in range(dim): - x = sym.nodes(dim) - - f = bind(discr, sym.sin(3*x[axis]))(actx) - df = bind(discr, 3*sym.cos(3*x[axis]))(actx) + df_num = op.local_grad(dcoll, f(x, axis))[axis] + df_volm = df(x, axis) - sym_op = nabla[axis](sym.var("f")) - bound_op = bind(discr, sym_op) - df_num = bound_op(f=f) - - linf_error = actx.np.linalg.norm(df_num - df, ord=np.inf) + linf_error = flat_norm(df_num - df_volm, ord=np.inf) axis_eoc_recs[axis].add_data_point(1/n, linf_error) for axis, eoc_rec in enumerate(axis_eoc_recs): @@ -473,25 +475,24 @@ def test_2d_gauss_theorem(actx_factory): actx = actx_factory() - discr = DiscretizationCollection(actx, mesh, order=2) + 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( - sym.sin(3*x[0])+sym.cos(3*x[1]), - sym.sin(2*x[0])+sym.cos(x[1])) + actx.np.sin(3*x[0]) + actx.np.cos(3*x[1]), + actx.np.sin(2*x[0]) + actx.np.cos(x[1]) + ) - gauss_err = bind(discr, - sym.integral(( - sym.nabla(2) * f(sym.nodes(2)) - ).sum()) - - # noqa: W504 - sym.integral( - sym.project("vol", BTAG_ALL)(f(sym.nodes(2))) - .dot(sym.normal(BTAG_ALL, 2)), - dd=BTAG_ALL) - )(actx) + f_volm = f(x_volm) + int_1 = op.integral(dcoll, "vol", op.local_div(dcoll, f_volm)) - assert abs(gauss_err) < 1e-13 + prj_f = op.project(dcoll, "vol", BTAG_ALL, f_volm) + normal = thaw(op.normal(dcoll, BTAG_ALL), actx) + int_2 = op.integral(dcoll, BTAG_ALL, prj_f.dot(normal)) + + assert abs(int_1 - int_2) < 1e-13 @pytest.mark.parametrize("mesh_name", ["2-1-ellipse", "spheroid"]) @@ -537,10 +538,10 @@ def test_surface_divergence_theorem(actx_factory, mesh_name, visualize=False): 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] + 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() @@ -571,65 +572,60 @@ def test_surface_divergence_theorem(actx_factory, mesh_name, visualize=False): from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory - discr = DiscretizationCollection( + + qtag = dof_desc.DISCR_TAG_QUAD + dcoll = DiscretizationCollection( actx, mesh, order=builder.order, discr_tag_to_group_factory={ - "product": QuadratureSimplexGroupFactory(2 * builder.order) + qtag: QuadratureSimplexGroupFactory(2 * builder.order) } ) - volume = discr.discr_from_dd(dof_desc.DD_VOLUME) + 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("product") + dq = dd.with_discr_tag(qtag) df = dof_desc.as_dofdesc(FACE_RESTR_ALL) - ambient_dim = discr.ambient_dim - dim = discr.dim + ambient_dim = dcoll.ambient_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() + f_num = f(thaw(op.nodes(dcoll, dd=dd), actx)) + f_quad_num = f(thaw(op.nodes(dcoll, dd=dq), actx)) - sym_face_normal = sym.normal(df, ambient_dim, dim=dim - 1) - sym_face_f = sym.project(dd, df)(sym_f) + from grudge.geometry import normal, summed_curvature + + kappa = summed_curvature(actx, dcoll, dd=dq) + normal = normal(actx, dcoll, dd=dq) + face_normal = thaw(op.normal(dcoll, df), actx) + face_f = op.project(dcoll, dd, df, f_num) # 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)) + 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 - 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) + 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 = bind(discr, sym.norm(np.inf, sym.var("x")))(actx, x=op_local) + err_local = op.norm(dcoll, op_local, np.inf) 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) + h_max = op.h_max_from_volume(dcoll) + 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) + vis = make_visualizer(dcoll) filename = f"surface_divergence_theorem_{mesh_name}_{i:04d}.vtu" vis.write_vtk_file(filename, [ @@ -727,39 +723,38 @@ def test_convergence_advec(actx_factory, mesh_name, mesh_pars, op_type, flux_typ norm_v = la.norm(v) def f(x): - return sym.sin(10*x) + return actx.np.sin(10*x) - def u_analytic(x): - return f( - -v.dot(x)/norm_v - + sym.var("t", dof_desc.DD_SCALAR)*norm_v) + def u_analytic(x, t=0): + return f(-v.dot(x)/norm_v + t*norm_v) from grudge.models.advection import ( - StrongAdvectionOperator, WeakAdvectionOperator) + StrongAdvectionOperator, WeakAdvectionOperator + ) from meshmode.mesh import BTAG_ALL - discr = DiscretizationCollection(actx, mesh, order=order) - op_class = { - "strong": StrongAdvectionOperator, - "weak": WeakAdvectionOperator, - }[op_type] - op = op_class(v, - inflow_u=u_analytic(sym.nodes(dim, BTAG_ALL)), - flux_type=flux_type) + dcoll = DiscretizationCollection(actx, mesh, order=order) + op_class = {"strong": StrongAdvectionOperator, + "weak": WeakAdvectionOperator}[op_type] + adv_operator = op_class(dcoll, v, + inflow_u=lambda t: u_analytic( + thaw(op.nodes(dcoll, dd=BTAG_ALL), actx), + t=t + ), + flux_type=flux_type) - bound_op = bind(discr, op.sym_operator()) - - u = bind(discr, u_analytic(sym.nodes(dim)))(actx, t=0) + nodes = thaw(op.nodes(dcoll), actx) + u = u_analytic(nodes, t=0) def rhs(t, u): - return bound_op(t=t, u=u) + return adv_operator.operator(t, u) if dim == 3: final_time = 0.1 else: final_time = 0.2 - h_max = bind(discr, sym.h_max_from_volume(discr.ambient_dim))(actx) + h_max = op.h_max_from_volume(dcoll, dim=dcoll.ambient_dim) dt = dt_factor * h_max/order**2 nsteps = (final_time // dt) + 1 dt = final_time/nsteps + 1e-15 @@ -770,7 +765,7 @@ def test_convergence_advec(actx_factory, mesh_name, mesh_pars, op_type, flux_typ last_u = None from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr, vis_order=order) + vis = make_visualizer(dcoll) step = 0 @@ -783,12 +778,16 @@ def test_convergence_advec(actx_factory, mesh_name, mesh_pars, op_type, flux_typ last_u = event.state_component if visualize: - vis.write_vtk_file("fld-%s-%04d.vtu" % (mesh_par, step), - [("u", event.state_component)]) - - error_l2 = bind(discr, - sym.norm(2, sym.var("u")-u_analytic(sym.nodes(dim))))( - t=last_t, u=last_u) + vis.write_vtk_file( + "fld-%s-%04d.vtu" % (mesh_par, step), + [("u", event.state_component)] + ) + + error_l2 = op.norm( + dcoll, + last_u - u_analytic(nodes, t=last_t), + 2 + ) logger.info("h_max %.5e error %.5e", h_max, error_l2) eoc_rec.add_data_point(h_max, error_l2) @@ -824,24 +823,32 @@ def test_convergence_maxwell(actx_factory, order): b=(1.0,)*dims, nelements_per_axis=(n,)*dims) - discr = DiscretizationCollection(actx, mesh, order=order) + dcoll = DiscretizationCollection(actx, mesh, order=order) epsilon = 1 mu = 1 from grudge.models.em import get_rectangular_cavity_mode - sym_mode = get_rectangular_cavity_mode(1, (1, 2, 2)) - analytic_sol = bind(discr, sym_mode) - fields = analytic_sol(actx, t=0, epsilon=epsilon, mu=mu) + def analytic_sol(x, t=0): + return get_rectangular_cavity_mode(actx, x, t, 1, (1, 2, 2)) + + nodes = thaw(op.nodes(dcoll), actx) + fields = analytic_sol(nodes, t=0) from grudge.models.em import MaxwellOperator - op = MaxwellOperator(epsilon, mu, flux_type=0.5, dimensions=dims) - op.check_bc_coverage(mesh) - bound_op = bind(discr, op.sym_operator()) + + maxwell_operator = MaxwellOperator( + dcoll, + epsilon, + mu, + flux_type=0.5, + dimensions=dims + ) + maxwell_operator.check_bc_coverage(mesh) def rhs(t, w): - return bound_op(t=t, w=w) + return maxwell_operator.operator(t, w) dt = 0.002 final_t = dt * 5 @@ -852,8 +859,6 @@ def test_convergence_maxwell(actx_factory, order): logger.info("dt %.5e nsteps %5d", dt, nsteps) - norm = bind(discr, sym.norm(2, sym.var("u"))) - step = 0 for event in dt_stepper.run(t_end=final_t): if isinstance(event, dt_stepper.StateComputed): @@ -863,9 +868,8 @@ def test_convergence_maxwell(actx_factory, order): step += 1 logger.debug("[%04d] t = %.5e", step, event.t) - sol = analytic_sol(actx, mu=mu, epsilon=epsilon, t=step * dt) - vals = [norm(u=(esc[i] - sol[i])) / norm(u=sol[i]) for i in range(5)] # noqa E501 - total_error = sum(vals) + sol = analytic_sol(nodes, t=step * dt) + total_error = op.norm(dcoll, esc - sol, 2) eoc_rec.add_data_point(1.0/n, total_error) logger.info("\n%s", eoc_rec.pretty_print( @@ -885,20 +889,15 @@ def test_improvement_quadrature(actx_factory, order): from grudge.models.advection import VariableCoefficientAdvectionOperator from pytools.convergence import EOCRecorder from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory + from meshmode.mesh import BTAG_ALL actx = actx_factory() dims = 2 - sym_nds = sym.nodes(dims) - advec_v = flat_obj_array(-1*sym_nds[1], sym_nds[0]) - - flux = "upwind" - op = VariableCoefficientAdvectionOperator(advec_v, 0, flux_type=flux) - def gaussian_mode(): + def gaussian_mode(x): source_width = 0.1 - sym_x = sym.nodes(2) - return sym.exp(-np.dot(sym_x, sym_x) / source_width**2) + return actx.np.exp(-np.dot(x, x) / source_width**2) def conv_test(descr, use_quad): logger.info("-" * 75) @@ -906,6 +905,11 @@ def test_improvement_quadrature(actx_factory, order): 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( @@ -916,22 +920,33 @@ def test_improvement_quadrature(actx_factory, order): if use_quad: discr_tag_to_group_factory = { - "product": QuadratureSimplexGroupFactory(order=4*order) + qtag: QuadratureSimplexGroupFactory(order=4*order) } else: - discr_tag_to_group_factory = {"product": None} + discr_tag_to_group_factory = {} - discr = DiscretizationCollection( + dcoll = DiscretizationCollection( actx, mesh, order=order, discr_tag_to_group_factory=discr_tag_to_group_factory ) - bound_op = bind(discr, op.sym_operator()) - fields = bind(discr, gaussian_mode())(actx, t=0) - norm = bind(discr, sym.norm(2, sym.var("u"))) + nodes = thaw(op.nodes(dcoll), actx) - esc = bound_op(u=fields) - total_error = norm(u=esc) + 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, total_error) logger.info("\n%s", eoc_rec.pretty_print( @@ -950,35 +965,6 @@ def test_improvement_quadrature(actx_factory, order): # }}} -# {{{ operator collector determinism - -def test_op_collector_order_determinism(): - class TestOperator(sym.Operator): - - def __init__(self): - sym.Operator.__init__(self, dof_desc.DD_VOLUME, dof_desc.DD_VOLUME) - - mapper_method = "map_test_operator" - - from grudge.symbolic.mappers import BoundOperatorCollector - - class TestBoundOperatorCollector(BoundOperatorCollector): - - def map_test_operator(self, expr): - return self.map_operator(expr) - - v0 = sym.var("v0") - ob0 = sym.OperatorBinding(TestOperator(), v0) - - v1 = sym.var("v1") - ob1 = sym.OperatorBinding(TestOperator(), v1) - - # The output order isn't significant, but it should always be the same. - assert list(TestBoundOperatorCollector(TestOperator)(ob0 + ob1)) == [ob0, ob1] - -# }}} - - # {{{ bessel def test_bessel(actx_factory): @@ -991,90 +977,33 @@ def test_bessel(actx_factory): b=(1.0,)*dims, nelements_per_axis=(8,)*dims) - discr = DiscretizationCollection(actx, mesh, order=3) + dcoll = DiscretizationCollection(actx, mesh, order=3) + + nodes = thaw(op.nodes(dcoll), actx) + r = actx.np.sqrt(nodes[0]**2 + nodes[1]**2) - nodes = sym.nodes(dims) - r = sym.cse(sym.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 = ( - sym.bessel_j(n+1, r) - + sym.bessel_j(n-1, r) - - 2*n/r * sym.bessel_j(n, r)) + bessel_zero = (bessel_j(actx, n+1, r) + + bessel_j(actx, n-1, r) + - 2*n/r * bessel_j(actx, n, r)) - z = bind(discr, sym.norm(2, bessel_zero))(actx) + z = op.norm(dcoll, bessel_zero, 2) assert z < 1e-15 # }}} -# {{{ function symbol - -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() - - -@pytest.mark.parametrize("array_type", ["scalar", "vector"]) -def test_function_symbol_array(actx_factory, array_type): - """Test if `FunctionSymbol` distributed properly over 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=4) - discr = DiscretizationCollection(actx, mesh, order=4) - volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) - - if array_type == "scalar": - sym_x = sym.var("x") - x = thaw(actx, actx.np.cos(volume_discr.nodes()[0])) - elif array_type == "vector": - sym_x = sym.make_sym_array("x", dim) - x = thaw(actx, volume_discr.nodes()) - else: - raise ValueError("unknown array type") - - norm = bind(discr, sym.norm(2, sym_x))(x=x) - assert isinstance(norm, float) - -# }}} - - @pytest.mark.parametrize("p", [2, np.inf]) def test_norm_obj_array(actx_factory, p): - """Test :func:`grudge.symbolic.operators.norm` for object arrays.""" + """Test :func:`grudge.op.norm` for object arrays.""" actx = actx_factory() @@ -1082,14 +1011,13 @@ def test_norm_obj_array(actx_factory, p): 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) + dcoll = 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 = op.norm(dcoll, w[0], p) norm_exact = w[0] logger.info("norm: %.5e %.5e", norm, norm_exact) @@ -1099,8 +1027,7 @@ def test_norm_obj_array(actx_factory, p): # {{{ vector - sym_w = sym.make_sym_array("w", dim) - norm = bind(discr, sym.norm(p, sym_w))(actx, w=w) + 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) @@ -1109,23 +1036,6 @@ def test_norm_obj_array(actx_factory, p): # }}} -def test_map_if(actx_factory): - """Test :meth:`grudge.symbolic.execution.ExecutionMapper.map_if` handling - of scalar conditions. - """ - - 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) - - sym_if = sym.If(sym.Comparison(2.0, "<", 1.0e-14), 1.0, 2.0) - bind(discr, sym_if)(actx) - - def test_empty_boundary(actx_factory): # https://github.com/inducer/grudge/issues/54 @@ -1137,96 +1047,12 @@ def test_empty_boundary(actx_factory): 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) + dcoll = DiscretizationCollection(actx, mesh, order=4) + normal = op.normal(dcoll, BTAG_NONE) 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_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) - - # }}} - - -@pytest.mark.parametrize("ambient_dim", [ - 2, - # FIXME, cf. https://github.com/inducer/grudge/pull/78/ - pytest.param(3, marks=pytest.mark.xfail) - ]) -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()) - - # }}} + assert len(component) == len(dcoll.discr_from_dd(BTAG_NONE).groups) # You can test individual routines by typing diff --git a/test/test_grudge_sym_old.py b/test/test_grudge_sym_old.py new file mode 100644 index 0000000000000000000000000000000000000000..ed47028f08716c51902a2c1b5d6bee7ef261c4b8 --- /dev/null +++ b/test/test_grudge_sym_old.py @@ -0,0 +1,942 @@ +__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np + +from meshmode import _acf # noqa: F401 +from meshmode.dof_array import flatten, thaw +import meshmode.mesh.generation as mgen + +from pytools.obj_array import flat_obj_array, make_obj_array + +from grudge import sym, bind, DiscretizationCollection + +import grudge.dof_desc as dof_desc + +import pytest +from meshmode.array_context import ( # noqa + pytest_generate_tests_for_pyopencl_array_context + as pytest_generate_tests) + +import logging + +logger = logging.getLogger(__name__) + + +# {{{ inverse metric + +@pytest.mark.parametrize("dim", [2, 3]) +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) + +# }}} + + +# {{{ mass operator trig integration + +@pytest.mark.parametrize("ambient_dim", [1, 2, 3]) +@pytest.mark.parametrize("discr_tag", [dof_desc.DISCR_TAG_BASE, + dof_desc.DISCR_TAG_QUAD]) +def test_mass_mat_trig(actx_factory, ambient_dim, discr_tag): + """Check the integral of some trig functions on an interval using the mass + matrix. + """ + actx = actx_factory() + + nel_1d = 16 + order = 4 + + a = -4.0 * np.pi + b = +9.0 * np.pi + true_integral = 13*np.pi/2 * (b - a)**(ambient_dim - 1) + + from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory + dd_quad = dof_desc.DOFDesc(dof_desc.DTAG_VOLUME_ALL, discr_tag) + if discr_tag is dof_desc.DISCR_TAG_BASE: + discr_tag_to_group_factory = {} + else: + discr_tag_to_group_factory = { + discr_tag: QuadratureSimplexGroupFactory(order=2*order) + } + + mesh = mgen.generate_regular_rect_mesh( + a=(a,)*ambient_dim, b=(b,)*ambient_dim, + nelements_per_axis=(nel_1d,)*ambient_dim, order=1) + discr = DiscretizationCollection( + actx, mesh, order=order, + discr_tag_to_group_factory=discr_tag_to_group_factory + ) + + def _get_variables_on(dd): + sym_f = sym.var("f", dd=dd) + sym_x = sym.nodes(ambient_dim, dd=dd) + sym_ones = sym.Ones(dd) + + return sym_f, sym_x, sym_ones + + sym_f, sym_x, sym_ones = _get_variables_on(dof_desc.DD_VOLUME) + f_volm = actx.to_numpy(flatten(bind(discr, sym.cos(sym_x[0])**2)(actx))) + ones_volm = actx.to_numpy(flatten(bind(discr, sym_ones)(actx))) + + sym_f, sym_x, sym_ones = _get_variables_on(dd_quad) + f_quad = bind(discr, sym.cos(sym_x[0])**2)(actx) + ones_quad = bind(discr, sym_ones)(actx) + + mass_op = bind(discr, sym.MassOperator(dd_quad, dof_desc.DD_VOLUME)(sym_f)) + + num_integral_1 = np.dot(ones_volm, actx.to_numpy(flatten(mass_op(f=f_quad)))) + err_1 = abs(num_integral_1 - true_integral) + assert err_1 < 1e-9, err_1 + + num_integral_2 = np.dot(f_volm, actx.to_numpy(flatten(mass_op(f=ones_quad)))) + err_2 = abs(num_integral_2 - true_integral) + assert err_2 < 1.0e-9, err_2 + + if discr_tag is dof_desc.DISCR_TAG_BASE: + # NOTE: `integral` always makes a square mass matrix and + # `QuadratureSimplexGroupFactory` does not have a `mass_matrix` method. + num_integral_3 = bind(discr, + sym.integral(sym_f, dd=dd_quad))(f=f_quad) + err_3 = abs(num_integral_3 - true_integral) + assert err_3 < 5.0e-10, err_3 + +# }}} + + +# {{{ mass operator surface area + +def _ellipse_surface_area(radius, aspect_ratio): + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.ellipe.html + eccentricity = 1.0 - (1/aspect_ratio)**2 + + if abs(aspect_ratio - 2.0) < 1.0e-14: + # NOTE: hardcoded value so we don't need scipy for the test + ellip_e = 1.2110560275684594 + else: + from scipy.special import ellipe # pylint: disable=no-name-in-module + ellip_e = ellipe(eccentricity) + + return 4.0 * radius * ellip_e + + +def _spheroid_surface_area(radius, aspect_ratio): + # https://en.wikipedia.org/wiki/Ellipsoid#Surface_area + a = 1.0 + c = aspect_ratio + + if a < c: + e = np.sqrt(1.0 - (a/c)**2) + return 2.0 * np.pi * radius**2 * (1.0 + (c/a) / e * np.arcsin(e)) + else: + e = np.sqrt(1.0 - (c/a)**2) + return 2.0 * np.pi * radius**2 * (1 + (c/a)**2 / e * np.arctanh(e)) + + +@pytest.mark.parametrize("name", [ + "2-1-ellipse", "spheroid", "box2d", "box3d" + ]) +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 + +# }}} + + +# {{{ surface mass inverse + +@pytest.mark.parametrize("name", ["2-1-ellipse", "spheroid"]) +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 + +# }}} + + +# {{{ surface face normal orthogonality + +@pytest.mark.parametrize("mesh_name", ["2-1-ellipse", "spheroid"]) +def test_face_normal_surface(actx_factory, mesh_name): + """Check that face normals are orthogonal to the surface normal""" + actx = actx_factory() + + # {{{ geometry + + if mesh_name == "2-1-ellipse": + from mesh_data import EllipseMeshBuilder + builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) + elif mesh_name == "spheroid": + from mesh_data import SpheroidMeshBuilder + builder = SpheroidMeshBuilder() + else: + raise ValueError("unknown mesh name: %s" % mesh_name) + + mesh = builder.get_mesh(builder.resolutions[0], builder.mesh_order) + discr = DiscretizationCollection(actx, mesh, order=builder.order) + + volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) + logger.info("ndofs: %d", volume_discr.ndofs) + logger.info("nelements: %d", volume_discr.mesh.nelements) + + # }}} + + # {{{ symbolic + from meshmode.discretization.connection import FACE_RESTR_INTERIOR + + dv = dof_desc.DD_VOLUME + df = dof_desc.as_dofdesc(FACE_RESTR_INTERIOR) + + ambient_dim = mesh.ambient_dim + dim = mesh.dim + + sym_surf_normal = sym.project(dv, df)( + sym.surface_normal(ambient_dim, dim=dim, dd=dv).as_vector() + ) + sym_surf_normal = sym_surf_normal / sym.sqrt(sum(sym_surf_normal**2)) + + sym_face_normal_i = sym.normal(df, ambient_dim, dim=dim - 1) + sym_face_normal_e = sym.OppositeInteriorFaceSwap(df)(sym_face_normal_i) + + if mesh.ambient_dim == 3: + # NOTE: there's only one face tangent in 3d + sym_face_tangent = ( + sym.pseudoscalar(ambient_dim, dim - 1, dd=df) + / sym.area_element(ambient_dim, dim - 1, dd=df)).as_vector() + + # }}} + + # {{{ checks + + def _eval_error(x): + return bind(discr, sym.norm(np.inf, sym.var("x", dd=df), dd=df))(actx, x=x) + + rtol = 1.0e-14 + + surf_normal = bind(discr, sym_surf_normal)(actx) + + face_normal_i = bind(discr, sym_face_normal_i)(actx) + face_normal_e = bind(discr, sym_face_normal_e)(actx) + + # check interpolated surface normal is orthogonal to face normal + error = _eval_error(surf_normal.dot(face_normal_i)) + logger.info("error[n_dot_i]: %.5e", error) + assert error < rtol + + # check angle between two neighboring elements + error = _eval_error(face_normal_i.dot(face_normal_e) + 1.0) + logger.info("error[i_dot_e]: %.5e", error) + assert error > rtol + + # check orthogonality with face tangent + if ambient_dim == 3: + face_tangent = bind(discr, sym_face_tangent)(actx) + + error = _eval_error(face_tangent.dot(face_normal_i)) + logger.info("error[t_dot_i]: %.5e", error) + assert error < 5 * rtol + + # }}} + +# }}} + + +# {{{ diff operator + +@pytest.mark.parametrize("dim", [1, 2, 3]) +def test_tri_diff_mat(actx_factory, dim, order=4): + """Check differentiation matrix along the coordinate axes on a disk + + Uses sines as the function to differentiate. + """ + + actx = actx_factory() + + from pytools.convergence import EOCRecorder + axis_eoc_recs = [EOCRecorder() for axis in range(dim)] + + for n in [4, 8, 16]: + mesh = mgen.generate_regular_rect_mesh(a=(-0.5,)*dim, b=(0.5,)*dim, + nelements_per_axis=(n,)*dim, order=4) + + discr = DiscretizationCollection(actx, mesh, order=4) + nabla = sym.nabla(dim) + + for axis in range(dim): + x = sym.nodes(dim) + + f = bind(discr, sym.sin(3*x[axis]))(actx) + df = bind(discr, 3*sym.cos(3*x[axis]))(actx) + + sym_op = nabla[axis](sym.var("f")) + bound_op = bind(discr, sym_op) + df_num = bound_op(f=f) + + linf_error = actx.np.linalg.norm(df_num - df, ord=np.inf) + axis_eoc_recs[axis].add_data_point(1/n, linf_error) + + for axis, eoc_rec in enumerate(axis_eoc_recs): + logger.info("axis %d\n%s", axis, eoc_rec) + assert eoc_rec.order_estimate() > order - 0.25 + +# }}} + + +# {{{ divergence theorem + +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() + + discr = DiscretizationCollection(actx, mesh, order=2) + + def f(x): + return flat_obj_array( + sym.sin(3*x[0])+sym.cos(3*x[1]), + sym.sin(2*x[0])+sym.cos(x[1])) + + gauss_err = bind(discr, + sym.integral(( + sym.nabla(2) * f(sym.nodes(2)) + ).sum()) + - # noqa: W504 + sym.integral( + sym.project("vol", BTAG_ALL)(f(sym.nodes(2))) + .dot(sym.normal(BTAG_ALL, 2)), + dd=BTAG_ALL) + )(actx) + + assert abs(gauss_err) < 1e-13 + + +@pytest.mark.parametrize("mesh_name", ["2-1-ellipse", "spheroid"]) +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 + +# }}} + + +# {{{ operator collector determinism + +def test_op_collector_order_determinism(): + class TestOperator(sym.Operator): + + def __init__(self): + sym.Operator.__init__(self, dof_desc.DD_VOLUME, dof_desc.DD_VOLUME) + + mapper_method = "map_test_operator" + + from grudge.symbolic.mappers import BoundOperatorCollector + + class TestBoundOperatorCollector(BoundOperatorCollector): + + def map_test_operator(self, expr): + return self.map_operator(expr) + + v0 = sym.var("v0") + ob0 = sym.OperatorBinding(TestOperator(), v0) + + v1 = sym.var("v1") + ob1 = sym.OperatorBinding(TestOperator(), v1) + + # The output order isn't significant, but it should always be the same. + assert list(TestBoundOperatorCollector(TestOperator)(ob0 + ob1)) == [ob0, ob1] + +# }}} + + +# {{{ bessel + +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) + + discr = DiscretizationCollection(actx, mesh, order=3) + + nodes = sym.nodes(dims) + r = sym.cse(sym.sqrt(nodes[0]**2 + nodes[1]**2)) + + # https://dlmf.nist.gov/10.6.1 + n = 3 + bessel_zero = ( + sym.bessel_j(n+1, r) + + sym.bessel_j(n-1, r) + - 2*n/r * sym.bessel_j(n, r)) + + z = bind(discr, sym.norm(2, bessel_zero))(actx) + + assert z < 1e-15 + +# }}} + + +# {{{ function symbol + +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() + + +@pytest.mark.parametrize("array_type", ["scalar", "vector"]) +def test_function_symbol_array(actx_factory, array_type): + """Test if `FunctionSymbol` distributed properly over 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=4) + discr = DiscretizationCollection(actx, mesh, order=4) + volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME) + + if array_type == "scalar": + sym_x = sym.var("x") + x = thaw(actx, actx.np.cos(volume_discr.nodes()[0])) + elif array_type == "vector": + sym_x = sym.make_sym_array("x", dim) + x = thaw(actx, volume_discr.nodes()) + else: + raise ValueError("unknown array type") + + norm = bind(discr, sym.norm(2, sym_x))(x=x) + assert isinstance(norm, float) + +# }}} + + +@pytest.mark.parametrize("p", [2, np.inf]) +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_map_if(actx_factory): + """Test :meth:`grudge.symbolic.execution.ExecutionMapper.map_if` handling + of scalar conditions. + """ + + 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) + + sym_if = sym.If(sym.Comparison(2.0, "<", 1.0e-14), 1.0, 2.0) + bind(discr, sym_if)(actx) + + +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_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) + + # }}} + + +@pytest.mark.parametrize("ambient_dim", [ + 2, + # FIXME, cf. https://github.com/inducer/grudge/pull/78/ + pytest.param(3, marks=pytest.mark.xfail) + ]) +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()) + + # }}} + + +# You can test individual routines by typing +# $ python test_grudge.py 'test_routine()' + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) + +# vim: fdm=marker diff --git a/test/test_modal_connections.py b/test/test_modal_connections.py index eeb9a9782f71ffc99cc41093ed84296a3155f599..0eb0b67f42a7f4b21e82850036aee9bd7ecb7862 100644 --- a/test/test_modal_connections.py +++ b/test/test_modal_connections.py @@ -21,10 +21,12 @@ THE SOFTWARE. """ -from meshmode.array_context import ( # noqa +from arraycontext import ( # noqa pytest_generate_tests_for_pyopencl_array_context as pytest_generate_tests ) +from arraycontext.container.traversal import thaw + from meshmode.discretization.poly_element import ( # Simplex group factories InterpolatoryQuadratureSimplexGroupFactory, @@ -34,8 +36,8 @@ from meshmode.discretization.poly_element import ( LegendreGaussLobattoTensorProductGroupFactory, # Quadrature-based (non-interpolatory) group factories QuadratureSimplexGroupFactory - ) -from meshmode.dof_array import thaw +) +from meshmode.dof_array import flat_norm import meshmode.mesh.generation as mgen from grudge import DiscretizationCollection @@ -74,7 +76,7 @@ def test_inverse_modal_connections(actx_factory, nodal_group_factory): 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]) + x_nodal = thaw(dcoll.discr_from_dd(dd_volume).nodes()[0], actx) nodal_f = f(x_nodal) # Map nodal coefficients of f to modal coefficients @@ -86,7 +88,7 @@ def test_inverse_modal_connections(actx_factory, nodal_group_factory): # This error should be small since we composed a map with # its inverse - err = actx.np.linalg.norm(nodal_f - nodal_f_2) + err = flat_norm(nodal_f - nodal_f_2) assert err <= 1e-13 @@ -117,7 +119,7 @@ def test_inverse_modal_connections_quadgrid(actx_factory): dd_quad = dof_desc.DOFDesc(dof_desc.DTAG_VOLUME_ALL, dof_desc.DISCR_TAG_QUAD) - x_quad = thaw(actx, dcoll.discr_from_dd(dd_quad).nodes()[0]) + 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 @@ -129,6 +131,6 @@ def test_inverse_modal_connections_quadgrid(actx_factory): # This error should be small since we composed a map with # its inverse - err = actx.np.linalg.norm(quad_f - quad_f_2) + err = flat_norm(quad_f - quad_f_2) assert err <= 1e-11 diff --git a/test/test_mpi_communication.py b/test/test_mpi_communication.py index 985b4beb9d2dacd3b7ed9c50d37799248d20e288..879d3bf87dc3d4ad8dfeb1c3512f1423ec1589f7 100644 --- a/test/test_mpi_communication.py +++ b/test/test_mpi_communication.py @@ -1,6 +1,7 @@ __copyright__ = """ Copyright (C) 2017 Ellis Hoag Copyright (C) 2017 Andreas Kloeckner +Copyright (C) 2021 University of Illinois Board of Trustees """ __license__ = """ @@ -29,15 +30,22 @@ import numpy as np import pyopencl as cl import logging -from meshmode.array_context import PyOpenCLArrayContext +from arraycontext.impl.pyopencl import PyOpenCLArrayContext +from arraycontext.container.traversal import thaw logger = logging.getLogger(__name__) logging.basicConfig() logger.setLevel(logging.INFO) -from grudge import sym, bind, DiscretizationCollection +from grudge import DiscretizationCollection from grudge.shortcuts import set_up_rk4 +from meshmode.dof_array import flat_norm + +from pytools.obj_array import flat_obj_array + +import grudge.op as op + def simple_mpi_communication_entrypoint(): cl_ctx = cl.create_some_context() @@ -65,30 +73,33 @@ def simple_mpi_communication_entrypoint(): else: local_mesh = mesh_dist.receive_mesh_part() - vol_discr = DiscretizationCollection(actx, local_mesh, order=5, + dcoll = 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) - ) + x = thaw(op.nodes(dcoll), actx) + myfunc = actx.np.sin(np.dot(x, [2, 3])) + + from grudge.dof_desc import as_dofdesc - hopefully_zero = bound_face_swap(myfunc=myfunc) - error = actx.np.linalg.norm(hopefully_zero, ord=np.inf) + 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 = flat_norm(hopefully_zero, ord=np.inf) print(__file__) with np.printoptions(threshold=100000000, suppress=True): @@ -124,79 +135,65 @@ def mpi_communication_entrypoint(): part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) + + del mesh else: local_mesh = mesh_dist.receive_mesh_part() - vol_discr = DiscretizationCollection(actx, local_mesh, order=order, - mpi_communicator=comm) - - source_center = np.array([0.1, 0.22, 0.33])[:local_mesh.dim] - source_width = 0.05 - source_omega = 3 - - sym_x = sym.nodes(local_mesh.dim) - sym_source_center_dist = sym_x - source_center - sym_t = sym.ScalarVariable("t") + dcoll = DiscretizationCollection(actx, local_mesh, order=order, + mpi_communicator=comm) + + def source_f(actx, dcoll, t=0): + source_center = np.array([0.1, 0.22, 0.33])[:dcoll.dim] + source_width = 0.05 + source_omega = 3 + nodes = thaw(op.nodes(dcoll), actx) + source_center_dist = flat_obj_array( + [nodes[i] - source_center[i] for i in range(dcoll.dim)] + ) + return ( + np.sin(source_omega*t) + * actx.np.exp( + -np.dot(source_center_dist, source_center_dist) + / source_width**2 + ) + ) from grudge.models.wave import WeakWaveOperator from meshmode.mesh import BTAG_ALL, BTAG_NONE - op = WeakWaveOperator(0.1, vol_discr.dim, - source_f=( - sym.sin(source_omega*sym_t) - * sym.exp( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2)), - dirichlet_tag=BTAG_NONE, - neumann_tag=BTAG_NONE, - radiation_tag=BTAG_ALL, - flux_type="upwind") - - from pytools.obj_array import flat_obj_array - fields = flat_obj_array(vol_discr.zeros(actx), - [vol_discr.zeros(actx) for i in range(vol_discr.dim)]) + + wave_op = WeakWaveOperator( + dcoll, + 0.1, + source_f=source_f, + dirichlet_tag=BTAG_NONE, + neumann_tag=BTAG_NONE, + radiation_tag=BTAG_ALL, + flux_type="upwind" + ) + + fields = flat_obj_array( + dcoll.zeros(actx), + [dcoll.zeros(actx) for i in range(dcoll.dim)] + ) # FIXME - # dt = op.estimate_rk4_timestep(vol_discr, fields=fields) + # dt = op.estimate_rk4_timestep(dcoll, fields=fields) - # FIXME: Should meshmode consider BTAG_PARTITION to be a boundary? - # Fails because: "found faces without boundary conditions" - # op.check_bc_coverage(local_mesh) + wave_op.check_bc_coverage(local_mesh) from logpyle import LogManager, \ add_general_quantities, \ - add_run_info, \ - IntervalTimer, EventCounter + add_run_info log_filename = None # NOTE: LogManager hangs when using a file on a shared directory. # log_filename = "grudge_log.dat" logmgr = LogManager(log_filename, "w", comm) add_run_info(logmgr) add_general_quantities(logmgr) - log_quantities =\ - {"rank_data_swap_timer": IntervalTimer("rank_data_swap_timer", - "Time spent evaluating RankDataSwapAssign"), - "rank_data_swap_counter": EventCounter("rank_data_swap_counter", - "Number of RankDataSwapAssign instructions evaluated"), - "exec_timer": IntervalTimer("exec_timer", - "Total time spent executing instructions"), - "insn_eval_timer": IntervalTimer("insn_eval_timer", - "Time spend evaluating instructions"), - "future_eval_timer": IntervalTimer("future_eval_timer", - "Time spent evaluating futures"), - "busy_wait_timer": IntervalTimer("busy_wait_timer", - "Time wasted doing busy wait")} - for quantity in log_quantities.values(): - logmgr.add_quantity(quantity) - - logger.debug("\n%s", sym.pretty(op.sym_operator())) - bound_op = bind(vol_discr, op.sym_operator()) def rhs(t, w): - val, rhs.profile_data = bound_op(profile_data=rhs.profile_data, - log_quantities=log_quantities, - t=t, w=w) - return val - rhs.profile_data = {} + return wave_op.operator(t, w) dt_stepper = set_up_rk4("w", dt, fields, rhs) @@ -204,12 +201,10 @@ def mpi_communication_entrypoint(): nsteps = int(final_t/dt) logger.info("[%04d] dt %.5e nsteps %4d", i_local_rank, dt, nsteps) - # from grudge.shortcuts import make_visualizer - # vis = make_visualizer(vol_discr, vis_order=order) - step = 0 - norm = bind(vol_discr, sym.norm(2, sym.var("u"))) + def norm(u): + return op.norm(dcoll, u, 2) from time import time t_last_step = time() @@ -220,33 +215,16 @@ def mpi_communication_entrypoint(): assert event.component_id == "w" step += 1 - logger.debug("[%04d] t = %.5e |u| = %.5e ellapsed %.5e", - step, event.t, - norm(u=event.state_component[0]), - time() - t_last_step) - - # if step % 10 == 0: - # vis.write_vtk_file("rank%d-fld-%04d.vtu" % (i_local_rank, step), - # [("u", event.state_component[0]), - # ("v", event.state_component[1:])]) + logger.info("[%04d] t = %.5e |u| = %.5e ellapsed %.5e", + step, event.t, + norm(u=event.state_component[0]), + time() - t_last_step) + t_last_step = time() logmgr.tick_after() logmgr.tick_before() - logmgr.tick_after() - def print_profile_data(data): - logger.info("""execute() for rank %d:\n - \tInstruction Evaluation: %g\n - \tFuture Evaluation: %g\n - \tBusy Wait: %g\n - \tTotal: %g seconds""", - i_local_rank, - data["insn_eval_time"] / data["total_time"] * 100, - data["future_eval_time"] / data["total_time"] * 100, - data["busy_wait_time"] / data["total_time"] * 100, - data["total_time"]) - - print_profile_data(rhs.profile_data) + logmgr.tick_after() logmgr.close() logger.info("Rank %d exiting", i_local_rank) @@ -261,6 +239,7 @@ def test_mpi(num_ranks): from subprocess import check_call import sys + # NOTE: CI uses OpenMPI; -x to pass env vars. MPICH uses -env check_call([ "mpiexec", "-np", str(num_ranks), "-x", "RUN_WITHIN_MPI=1", @@ -276,6 +255,7 @@ def test_simple_mpi(num_ranks): from subprocess import check_call import sys + # NOTE: CI uses OpenMPI; -x to pass env vars. MPICH uses -env check_call([ "mpiexec", "-np", str(num_ranks), "-x", "RUN_WITHIN_MPI=1", diff --git a/test/test_op.py b/test/test_op.py index d74cdfe6ce7a7682dfb12aca506d68f53807199c..2d12409ee3a20c8cfce8c02a539dbd7e31f9e2ed 100644 --- a/test/test_op.py +++ b/test/test_op.py @@ -24,7 +24,6 @@ THE SOFTWARE. import numpy as np import meshmode.mesh.generation as mgen -from meshmode.dof_array import thaw from pytools.obj_array import make_obj_array @@ -32,9 +31,12 @@ from grudge import op, DiscretizationCollection from grudge.dof_desc import DOFDesc import pytest -from meshmode.array_context import ( # noqa - pytest_generate_tests_for_pyopencl_array_context - as pytest_generate_tests) + +from arraycontext import ( # noqa + pytest_generate_tests_for_pyopencl_array_context + as pytest_generate_tests +) +from arraycontext.container.traversal import thaw import logging @@ -68,25 +70,25 @@ def test_gradient(actx_factory, form, dim, order, vectorize, nested, def f(x): result = dcoll.zeros(actx) + 1 for i in range(dim-1): - result *= actx.np.sin(np.pi*x[i]) - result *= actx.np.cos(np.pi/2*x[dim-1]) + result = result * actx.np.sin(np.pi*x[i]) + result = result * actx.np.cos(np.pi/2*x[dim-1]) return result def grad_f(x): result = make_obj_array([dcoll.zeros(actx) + 1 for _ in range(dim)]) for i in range(dim-1): for j in range(i): - result[i] *= actx.np.sin(np.pi*x[j]) - result[i] *= np.pi*actx.np.cos(np.pi*x[i]) + result[i] = result[i] * actx.np.sin(np.pi*x[j]) + result[i] = result[i] * np.pi*actx.np.cos(np.pi*x[i]) for j in range(i+1, dim-1): - result[i] *= actx.np.sin(np.pi*x[j]) - result[i] *= actx.np.cos(np.pi/2*x[dim-1]) + result[i] = result[i] * actx.np.sin(np.pi*x[j]) + result[i] = result[i] * actx.np.cos(np.pi/2*x[dim-1]) for j in range(dim-1): - result[dim-1] *= actx.np.sin(np.pi*x[j]) - result[dim-1] *= -np.pi/2*actx.np.sin(np.pi/2*x[dim-1]) + result[dim-1] = result[dim-1] * actx.np.sin(np.pi*x[j]) + result[dim-1] = result[dim-1] * (-np.pi/2*actx.np.sin(np.pi/2*x[dim-1])) return result - x = thaw(actx, op.nodes(dcoll)) + x = thaw(op.nodes(dcoll), actx) if vectorize: u = make_obj_array([(i+1)*f(x) for i in range(dim)]) @@ -96,7 +98,7 @@ def test_gradient(actx_factory, form, dim, order, vectorize, nested, def get_flux(u_tpair): dd = u_tpair.dd dd_allfaces = dd.with_dtag("all_faces") - normal = thaw(actx, op.normal(dcoll, dd)) + normal = thaw(op.normal(dcoll, dd), actx) u_avg = u_tpair.avg if vectorize: if nested: @@ -121,8 +123,10 @@ def test_gradient(actx_factory, form, dim, order, vectorize, nested, op.face_mass(dcoll, dd_allfaces, # Note: no boundary flux terms here because u_ext == u_int == 0 - get_flux(op.interior_trace_pair(dcoll, u))) + sum(get_flux(utpair) + for utpair in op.interior_trace_pairs(dcoll, u)) ) + ) else: raise ValueError("Invalid form argument.") @@ -186,8 +190,8 @@ def test_divergence(actx_factory, form, dim, order, vectorize, nested, def f(x): result = make_obj_array([dcoll.zeros(actx) + (i+1) for i in range(dim)]) for i in range(dim-1): - result *= actx.np.sin(np.pi*x[i]) - result *= actx.np.cos(np.pi/2*x[dim-1]) + result = result * actx.np.sin(np.pi*x[i]) + result = result * actx.np.cos(np.pi/2*x[dim-1]) return result def div_f(x): @@ -195,20 +199,20 @@ def test_divergence(actx_factory, form, dim, order, vectorize, nested, for i in range(dim-1): deriv = dcoll.zeros(actx) + (i+1) for j in range(i): - deriv *= actx.np.sin(np.pi*x[j]) - deriv *= np.pi*actx.np.cos(np.pi*x[i]) + deriv = deriv * actx.np.sin(np.pi*x[j]) + deriv = deriv * np.pi*actx.np.cos(np.pi*x[i]) for j in range(i+1, dim-1): - deriv *= actx.np.sin(np.pi*x[j]) - deriv *= actx.np.cos(np.pi/2*x[dim-1]) - result += deriv + deriv = deriv * actx.np.sin(np.pi*x[j]) + deriv = deriv * actx.np.cos(np.pi/2*x[dim-1]) + result = result + deriv deriv = dcoll.zeros(actx) + dim for j in range(dim-1): - deriv *= actx.np.sin(np.pi*x[j]) - deriv *= -np.pi/2*actx.np.sin(np.pi/2*x[dim-1]) - result += deriv + deriv = deriv * actx.np.sin(np.pi*x[j]) + deriv = deriv * (-np.pi/2*actx.np.sin(np.pi/2*x[dim-1])) + result = result + deriv return result - x = thaw(actx, op.nodes(dcoll)) + x = thaw(op.nodes(dcoll), actx) if vectorize: u = make_obj_array([(i+1)*f(x) for i in range(dim)]) @@ -220,7 +224,7 @@ def test_divergence(actx_factory, form, dim, order, vectorize, nested, def get_flux(u_tpair): dd = u_tpair.dd dd_allfaces = dd.with_dtag("all_faces") - normal = thaw(actx, op.normal(dcoll, dd)) + normal = thaw(op.normal(dcoll, dd), actx) flux = u_tpair.avg @ normal return op.project(dcoll, dd, dd_allfaces, flux) @@ -238,8 +242,10 @@ def test_divergence(actx_factory, form, dim, order, vectorize, nested, op.face_mass(dcoll, dd_allfaces, # Note: no boundary flux terms here because u_ext == u_int == 0 - get_flux(op.interior_trace_pair(dcoll, u))) + sum(get_flux(utpair) + for utpair in op.interior_trace_pairs(dcoll, u)) ) + ) else: raise ValueError("Invalid form argument.") diff --git a/unported-examples/advection/advection.py b/unported-examples/advection/advection.py deleted file mode 100644 index 80cfa0a99fa95ee5bdbc9e636906d3ae784064ea..0000000000000000000000000000000000000000 --- a/unported-examples/advection/advection.py +++ /dev/null @@ -1,183 +0,0 @@ -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la - - - - -def main(write_output=True, flux_type_arg="upwind"): - from grudge.tools import mem_checkpoint - from math import sin, cos, pi, sqrt - from math import floor - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - def f(x): - return sin(pi*x) - - def u_analytic(x, el, t): - return f((-numpy.dot(v, x)/norm_v+t*norm_v)) - - def boundary_tagger(vertices, el, face_nr, all_v): - if numpy.dot(el.face_normals[face_nr], v) < 0: - return ["inflow"] - else: - return ["outflow"] - - dim = 2 - - if dim == 1: - v = numpy.array([1]) - if rcon.is_head_rank: - from grudge.mesh.generator import make_uniform_1d_mesh - mesh = make_uniform_1d_mesh(0, 2, 10, periodic=True) - elif dim == 2: - v = numpy.array([2,0]) - if rcon.is_head_rank: - from grudge.mesh.generator import make_disk_mesh - mesh = make_disk_mesh(boundary_tagger=boundary_tagger) - elif dim == 3: - v = numpy.array([0,0,1]) - if rcon.is_head_rank: - from grudge.mesh.generator import make_cylinder_mesh, make_ball_mesh, make_box_mesh - - mesh = make_cylinder_mesh(max_volume=0.04, height=2, boundary_tagger=boundary_tagger, - periodic=False, radial_subdivisions=32) - else: - raise RuntimeError("bad number of dimensions") - - norm_v = la.norm(v) - - if rcon.is_head_rank: - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - if dim != 1: - mesh_data = mesh_data.reordered_by("cuthill") - - discr = rcon.make_discretization(mesh_data, order=4) - vis_discr = discr - - from grudge.visualization import VtkVisualizer - if write_output: - vis = VtkVisualizer(vis_discr, rcon, "fld") - - # operator setup ---------------------------------------------------------- - from grudge.data import \ - ConstantGivenFunction, \ - TimeConstantGivenFunction, \ - TimeDependentGivenFunction - from grudge.models.advection import StrongAdvectionOperator, WeakAdvectionOperator - op = WeakAdvectionOperator(v, - inflow_u=TimeDependentGivenFunction(u_analytic), - flux_type=flux_type_arg) - - u = discr.interpolate_volume_function(lambda x, el: u_analytic(x, el, 0)) - - # timestep setup ---------------------------------------------------------- - from grudge.timestep.runge_kutta import LSRK4TimeStepper - stepper = LSRK4TimeStepper() - - if rcon.is_head_rank: - print("%d elements" % len(discr.mesh.elements)) - - # diagnostics setup ------------------------------------------------------- - from logpyle import LogManager, \ - add_general_quantities, \ - add_simulation_quantities, \ - add_run_info - - if write_output: - log_file_name = "advection.dat" - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - - stepper.add_instrumentation(logmgr) - - from grudge.log import Integral, LpNorm - u_getter = lambda: u - logmgr.add_quantity(Integral(u_getter, discr, name="int_u")) - logmgr.add_quantity(LpNorm(u_getter, discr, p=1, name="l1_u")) - logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) - - logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) - - # timestep loop ----------------------------------------------------------- - rhs = op.bind(discr) - - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=3, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, fields=u)) - - for step, t, dt in step_it: - if step % 5 == 0 and write_output: - visf = vis.make_file("fld-%04d" % step) - vis.add_data(visf, [ - ("u", discr.convert_volume(u, kind="numpy")), - ], time=t, step=step) - visf.close() - - u = stepper(u, t, dt, rhs) - - true_u = discr.interpolate_volume_function(lambda x, el: u_analytic(x, el, t)) - print(discr.norm(u-true_u)) - assert discr.norm(u-true_u) < 1e-2 - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - - - - -if __name__ == "__main__": - main() - - - - -# entry points for py.test ---------------------------------------------------- -def test_advection(): - from pytools.test import mark_test - mark_long = mark_test.long - - for flux_type in ["upwind", "central", "lf"]: - yield "advection with %s flux" % flux_type, \ - mark_long(main), False, flux_type diff --git a/unported-examples/benchmark_grudge/benchmark_mpi.py b/unported-examples/benchmark_grudge/benchmark_mpi.py deleted file mode 100644 index 8c60ea3cf867b00432e3f992c7815572ce45f33d..0000000000000000000000000000000000000000 --- a/unported-examples/benchmark_grudge/benchmark_mpi.py +++ /dev/null @@ -1,134 +0,0 @@ -import os -import numpy as np -import pyopencl as cl - -from grudge import sym, bind, DiscretizationCollection -from grudge.shortcuts import set_up_rk4 - - -def simple_wave_entrypoint(dim=2, num_elems=256, order=4, num_steps=30, - log_filename="grudge.dat"): - cl_ctx = cl.create_some_context() - queue = cl.CommandQueue(cl_ctx) - - from mpi4py import MPI - comm = MPI.COMM_WORLD - num_parts = comm.Get_size() - n = int(num_elems ** (1./dim)) - - from meshmode.distributed import MPIMeshDistributor - 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=(-0.5,)*dim, - b=(0.5,)*dim, - n=(n,)*dim) - - from pymetis import part_graph - _, p = part_graph(num_parts, - xadj=mesh.nodal_adjacency.neighbors_starts.tolist(), - adjncy=mesh.nodal_adjacency.neighbors.tolist()) - part_per_element = np.array(p) - - local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) - else: - local_mesh = mesh_dist.receive_mesh_part() - - vol_discr = DiscretizationCollection(cl_ctx, local_mesh, order=order, - mpi_communicator=comm) - - source_center = np.array([0.1, 0.22, 0.33])[:local_mesh.dim] - source_width = 0.05 - source_omega = 3 - - sym_x = sym.nodes(local_mesh.dim) - sym_source_center_dist = sym_x - source_center - sym_t = sym.ScalarVariable("t") - - from grudge.models.wave import StrongWaveOperator - from meshmode.mesh import BTAG_ALL, BTAG_NONE - op = StrongWaveOperator(-0.1, vol_discr.dim, - source_f=( - sym.sin(source_omega*sym_t) - * sym.exp( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2)), - dirichlet_tag=BTAG_NONE, - neumann_tag=BTAG_NONE, - radiation_tag=BTAG_ALL, - flux_type="upwind") - - from pytools.obj_array import join_fields - fields = join_fields(vol_discr.zeros(queue), - [vol_discr.zeros(queue) for i in range(vol_discr.dim)]) - - from logpyle import LogManager, \ - add_general_quantities, \ - add_run_info, \ - IntervalTimer, EventCounter - # NOTE: LogManager hangs when using a file on a shared directory. - logmgr = LogManager(log_filename, "w", comm) - add_run_info(logmgr) - add_general_quantities(logmgr) - log_quantities =\ - {"rank_data_swap_timer": IntervalTimer("rank_data_swap_timer", - "Time spent evaluating RankDataSwapAssign"), - "rank_data_swap_counter": EventCounter("rank_data_swap_counter", - "Number of RankDataSwapAssign instructions evaluated"), - "exec_timer": IntervalTimer("exec_timer", - "Total time spent executing instructions"), - "insn_eval_timer": IntervalTimer("insn_eval_timer", - "Time spend evaluating instructions"), - "future_eval_timer": IntervalTimer("future_eval_timer", - "Time spent evaluating futures"), - "busy_wait_timer": IntervalTimer("busy_wait_timer", - "Time wasted doing busy wait")} - for quantity in log_quantities.values(): - logmgr.add_quantity(quantity) - - bound_op = bind(vol_discr, op.sym_operator()) - - def rhs(t, w): - val, rhs.profile_data = bound_op(queue, profile_data=rhs.profile_data, - log_quantities=log_quantities, - t=t, w=w) - return val - rhs.profile_data = {} - - dt = 0.04 - dt_stepper = set_up_rk4("w", dt, fields, rhs) - - logmgr.tick_before() - for event in dt_stepper.run(t_end=dt * num_steps): - if isinstance(event, dt_stepper.StateComputed): - logmgr.tick_after() - logmgr.tick_before() - logmgr.tick_after() - - def print_profile_data(data): - print("""execute() for rank %d: - \tInstruction Evaluation: %f%% - \tFuture Evaluation: %f%% - \tBusy Wait: %f%% - \tTotal: %f seconds""" % - (comm.Get_rank(), - data['insn_eval_time'] / data['total_time'] * 100, - data['future_eval_time'] / data['total_time'] * 100, - data['busy_wait_time'] / data['total_time'] * 100, - data['total_time'])) - - print_profile_data(rhs.profile_data) - logmgr.close() - - -if __name__ == "__main__": - assert "RUN_WITHIN_MPI" in os.environ, "Must run within mpi" - import sys - assert len(sys.argv) == 5, \ - "Usage: %s %s num_elems order num_steps logfile" \ - % (sys.executable, sys.argv[0]) - simple_wave_entrypoint(num_elems=int(sys.argv[1]), - order=int(sys.argv[2]), - num_steps=int(sys.argv[3]), - log_filename=sys.argv[4]) diff --git a/unported-examples/benchmark_grudge/run_benchmark.sh b/unported-examples/benchmark_grudge/run_benchmark.sh deleted file mode 100755 index 72eaca2bdd62d934644333f8d78b5628df8217e9..0000000000000000000000000000000000000000 --- a/unported-examples/benchmark_grudge/run_benchmark.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash - -# Weak scaling: We run our code on one computer, then we buy a second computer -# and we can run twice as much code in the same amount of time. - -# Strong scaling: We run our code on one computer, then we buy a second computer -# and we can run the same code in half the time. - -# Examples: -# ./run_benchmark.sh -t WEAK -n 100 -r 20 -s 1000 -l ~/weak_scaling.dat -o weak_scaling.txt -# ./run_benchmark.sh -t STRONG -n 100 -r 20 -s 1000 -l ~/strong_scaling.dat -o strong_scaling.txt - -set -eu - -# NOTE: benchmark_mpi.py hangs when logfile is in a shared directory. -USAGE="Usage: $0 -t <WEAK|STRONG> -n num_elems -r order -s num_steps -l logfile -o outfile" -while getopts "t:n:r:s:l:o:" OPT; do - case $OPT in - t) - case $OPTARG in - WEAK) - SCALING_TYPE='WEAK' - ;; - STRONG) - SCALING_TYPE='STRONG' - ;; - *) - echo $USAGE - exit 1 - ;; - esac - ;; - n) - NUM_ELEMS=$OPTARG - ;; - r) - ORDER=$OPTARG - ;; - s) - NUM_STEPS=$OPTARG - ;; - l) - LOGFILE=$OPTARG - ;; - o) - OUTFILE=$OPTARG - ;; - *) - echo $USAGE - exit 1 - ;; - esac -done - - -# NOTE: We want to make sure we run grudge in the right environment. -SHARED="/home/eshoag2/shared" -source $SHARED/miniconda3/bin/activate inteq -PYTHON=$(which python) -BENCHMARK_MPI="$SHARED/grudge/examples/benchmark_grudge/benchmark_mpi.py" - -# Assume HOSTS_LIST is sorted in increasing order starting with one host. -HOSTS_LIST="\ -porter \ -porter,stout \ -porter,stout,koelsch" - -ENVIRONMENT_VARS="\ --x RUN_WITHIN_MPI=1 \ --x PYOPENCL_CTX=0 \ --x POCL_AFFINITY=1" - -PERF_EVENTS="\ -cpu-cycles,\ -instructions,\ -task-clock" - -TEMPDIR=$(mktemp -d) -trap 'rm -rf $TEMPDIR' EXIT HUP INT QUIT TERM - -echo "$(date): Testing $SCALING_TYPE scaling" | tee -a $OUTFILE - -NUM_HOSTS=1 -BASE_NUM_ELEMS=$NUM_ELEMS -for HOSTS in $HOSTS_LIST; do - - if [ $SCALING_TYPE = 'WEAK' ]; then - NUM_ELEMS=$(echo $BASE_NUM_ELEMS $NUM_HOSTS | awk '{ print $1 * $2 }') - fi - - BENCHMARK_CMD="$PYTHON $BENCHMARK_MPI $NUM_ELEMS $ORDER $NUM_STEPS $LOGFILE.trial$NUM_HOSTS" - # NOTE: mpiexec recently updated so some things might act weird. - MPI_CMD="mpiexec --output-filename $TEMPDIR -H $HOSTS $ENVIRONMENT_VARS $BENCHMARK_CMD" - echo "Executing: $MPI_CMD" - - # NOTE: perf does not follow mpi accross different nodes. - # Instead, perf will follow all processes on the porter node. - echo "====================Using $NUM_HOSTS host(s)===================" >> $OUTFILE - START_TIME=$(date +%s) - perf stat --append -o $OUTFILE -e $PERF_EVENTS $MPI_CMD - DURATION=$(($(date +%s) - $START_TIME)) - echo "Finished in $DURATION seconds" - - echo "===================Output of Python===================" >> $OUTFILE - find $TEMPDIR -type f -exec cat {} \; >> $OUTFILE - echo "======================================================" >> $OUTFILE - rm -rf $TEMPDIR/* - - if [ $NUM_HOSTS -eq 1 ]; then - BASE_DURATION=$DURATION - fi - - # Efficiency is expected / actual - if [ $SCALING_TYPE = 'STRONG' ]; then - EFFICIENCY=$(echo $DURATION $BASE_DURATION $NUM_HOSTS | awk '{ print $2 / ($3 * $1) * 100"%" }') - elif [ $SCALING_TYPE = 'WEAK' ]; then - EFFICIENCY=$(echo $DURATION $BASE_DURATION | awk '{ print $2 / $1 * 100"%" }') - fi - - echo "Efficiency for $SCALING_TYPE scaling is $EFFICIENCY for $NUM_HOSTS host(s)." | tee -a $OUTFILE - - ((NUM_HOSTS++)) -done diff --git a/unported-examples/burgers/burgers.py b/unported-examples/burgers/burgers.py deleted file mode 100644 index 532e32869a9f5aa8fdb2065368aaab0deb92599a..0000000000000000000000000000000000000000 --- a/unported-examples/burgers/burgers.py +++ /dev/null @@ -1,246 +0,0 @@ -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la -from math import sin, cos, pi, sqrt -from pytools.test import mark_test - - - - -class ExactTestCase: - a = 0 - b = 150 - final_time = 1000 - - def u0(self, x): - return self.u_exact(x, 0) - - def u_exact(self, x, t): - # CAUTION: This gets the shock speed wrong as soon as the pulse - # starts interacting with itself. - - def f(x, shock_loc): - if x < (t-40)/4: - return 1/4 - else: - if t < 40: - if x < (3*t)/4: - return (x+15)/(t+20) - elif x < (t+80)/4: - return (x-30)/(t-40) - else: - return 1/4 - else: - if x < shock_loc: - return (x+15)/(t+20) - else: - return 1/4 - - from math import sqrt - - shock_loc = 30*sqrt(2*t+40)/sqrt(120) + t/4 - 10 - shock_win = (shock_loc + 20) // self.b - x += shock_win * 150 - - x -= 20 - - return max(f(x, shock_loc), f(x-self.b, shock_loc-self.b)) - -class OffCenterMigratingTestCase: - a = -pi - b = pi - final_time = 10 - - def u0(self, x): - return -0.4+sin(x+0.1) - - -class CenteredStationaryTestCase: - # does funny things to P-P - a = -pi - b = pi - final_time = 10 - - def u0(self, x): - return -sin(x) - -class OffCenterStationaryTestCase: - # does funny things to P-P - a = -pi - b = pi - final_time = 10 - - def u0(self, x): - return -sin(x+0.3) - - - -def main(write_output=True, flux_type_arg="upwind", - #case = CenteredStationaryTestCase(), - #case = OffCenterStationaryTestCase(), - #case = OffCenterMigratingTestCase(), - case = ExactTestCase(), - ): - from grudge.backends import guess_run_context - rcon = guess_run_context() - - order = 3 - if rcon.is_head_rank: - if True: - from grudge.mesh.generator import make_uniform_1d_mesh - mesh = make_uniform_1d_mesh(case.a, case.b, 20, periodic=True) - else: - from grudge.mesh.generator import make_rect_mesh - print((pi*2)/(11*5*2)) - mesh = make_rect_mesh((-pi, -1), (pi, 1), - periodicity=(True, True), - subdivisions=(11,5), - max_area=(pi*2)/(11*5*2) - ) - - if rcon.is_head_rank: - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - discr = rcon.make_discretization(mesh_data, order=order, - quad_min_degrees={"quad": 3*order}) - - if write_output: - from grudge.visualization import VtkVisualizer - vis = VtkVisualizer(discr, rcon, "fld") - - # operator setup ---------------------------------------------------------- - from grudge.second_order import IPDGSecondDerivative - - from grudge.models.burgers import BurgersOperator - op = BurgersOperator(mesh.dimensions, - viscosity_scheme=IPDGSecondDerivative()) - - if rcon.is_head_rank: - print("%d elements" % len(discr.mesh.elements)) - - # exact solution ---------------------------------------------------------- - import pymbolic - var = pymbolic.var - - u = discr.interpolate_volume_function(lambda x, el: case.u0(x[0])) - - # diagnostics setup ------------------------------------------------------- - from logpyle import LogManager, \ - add_general_quantities, \ - add_simulation_quantities, \ - add_run_info - - if write_output: - log_file_name = "burgers.dat" - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - - from grudge.log import LpNorm - u_getter = lambda: u - logmgr.add_quantity(LpNorm(u_getter, discr, p=1, name="l1_u")) - - logmgr.add_watches(["step.max", "t_sim.max", "l1_u", "t_step.max"]) - - # timestep loop ----------------------------------------------------------- - rhs = op.bind(discr) - - from grudge.timestep.runge_kutta import ODE45TimeStepper, LSRK4TimeStepper - stepper = ODE45TimeStepper() - - stepper.add_instrumentation(logmgr) - - try: - from grudge.timestep import times_and_steps - # for visc=0.01 - #stab_fac = 0.1 # RK4 - #stab_fac = 1.6 # dumka3(3), central - #stab_fac = 3 # dumka3(4), central - - #stab_fac = 0.01 # RK4 - stab_fac = 0.2 # dumka3(3), central - #stab_fac = 3 # dumka3(4), central - - dt = stab_fac*op.estimate_timestep(discr, - stepper=LSRK4TimeStepper(), t=0, fields=u) - - step_it = times_and_steps( - final_time=case.final_time, logmgr=logmgr, max_dt_getter=lambda t: dt) - from grudge.symbolic import InverseVandermondeOperator - inv_vdm = InverseVandermondeOperator().bind(discr) - - for step, t, dt in step_it: - if step % 3 == 0 and write_output: - if hasattr(case, "u_exact"): - extra_fields = [ - ("u_exact", - discr.interpolate_volume_function( - lambda x, el: case.u_exact(x[0], t)))] - else: - extra_fields = [] - - visf = vis.make_file("fld-%04d" % step) - vis.add_data(visf, [ - ("u", u), - ] + extra_fields, - time=t, - step=step) - visf.close() - - u = stepper(u, t, dt, rhs) - - if isinstance(case, ExactTestCase): - assert discr.norm(u, 1) < 50 - - finally: - if write_output: - vis.close() - - logmgr.save() - - - - -if __name__ == "__main__": - main() - - - - -# entry points for py.test ---------------------------------------------------- -@mark_test.long -def test_stability(): - main(write_output=False) - diff --git a/unported-examples/gas_dynamics/box-in-box.py b/unported-examples/gas_dynamics/box-in-box.py deleted file mode 100644 index a6b9aaf7bbd232ed8286045d0e54289b447d3d90..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/box-in-box.py +++ /dev/null @@ -1,242 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy - - -def make_boxmesh(): - from meshpy.tet import MeshInfo, build - from meshpy.geometry import GeometryBuilder, Marker, make_box - - geob = GeometryBuilder() - - box_marker = Marker.FIRST_USER_MARKER - extent_small = 0.1*numpy.ones(3, dtype=numpy.float64) - - geob.add_geometry(*make_box(-extent_small, extent_small)) - - # make small "separator box" for region attribute - - geob.add_geometry( - *make_box( - -extent_small*4, - numpy.array([4, 0.4, 0.4], dtype=numpy.float64))) - - geob.add_geometry( - *make_box( - numpy.array([-1, -1, -1], dtype=numpy.float64), - numpy.array([5, 1, 1], dtype=numpy.float64))) - - mesh_info = MeshInfo() - geob.set(mesh_info) - mesh_info.set_holes([(0, 0, 0)]) - - # region attributes - mesh_info.regions.resize(1) - mesh_info.regions[0] = ( - # point in region - list(extent_small*2) + [ - # region number - 1, - # max volume in region - #0.0001 - 0.005 - ]) - - mesh = build(mesh_info, max_volume=0.02, - volume_constraints=True, attributes=True) - print("%d elements" % len(mesh.elements)) - #mesh.write_vtk("box-in-box.vtk") - #print "done writing" - - fvi2fm = mesh.face_vertex_indices_to_face_marker - - face_marker_to_tag = { - box_marker: "noslip", - Marker.MINUS_X: "inflow", - Marker.PLUS_X: "outflow", - Marker.MINUS_Y: "inflow", - Marker.PLUS_Y: "inflow", - Marker.PLUS_Z: "inflow", - Marker.MINUS_Z: "inflow" - } - - def bdry_tagger(fvi, el, fn, all_v): - face_marker = fvi2fm[fvi] - return [face_marker_to_tag[face_marker]] - - from grudge.mesh import make_conformal_mesh - return make_conformal_mesh( - mesh.points, mesh.elements, bdry_tagger) - - -def main(): - from grudge.backends import guess_run_context - rcon = guess_run_context(["cuda"]) - - if rcon.is_head_rank: - mesh = make_boxmesh() - #from grudge.mesh import make_rect_mesh - #mesh = make_rect_mesh( - # boundary_tagger=lambda fvi, el, fn, all_v: ["inflow"]) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [3]: - from pytools import add_python_path_relative_to_script - add_python_path_relative_to_script("..") - - from gas_dynamics_initials import UniformMachFlow - box = UniformMachFlow(angle_of_attack=0) - - from grudge.models.gas_dynamics import GasDynamicsOperator - op = GasDynamicsOperator(dimensions=3, - gamma=box.gamma, mu=box.mu, - prandtl=box.prandtl, spec_gas_const=box.spec_gas_const, - bc_inflow=box, bc_outflow=box, bc_noslip=box, - inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") - - discr = rcon.make_discretization(mesh_data, order=order, - debug=[ - #"cuda_no_plan", - #"cuda_dump_kernels", - #"dump_dataflow_graph", - #"dump_optemplate_stages", - #"dump_dataflow_graph", - #"print_op_code", - "cuda_no_plan_el_local", - ], - default_scalar_type=numpy.float32, - tune_for=op.sym_operator()) - - from grudge.visualization import SiloVisualizer, VtkVisualizer # noqa - #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) - vis = SiloVisualizer(discr, rcon) - - fields = box.volume_interpolant(0, discr) - - navierstokes_ex = op.bind(discr) - - max_eigval = [0] - - def rhs(t, q): - ode_rhs, speed = navierstokes_ex(t, q) - max_eigval[0] = speed - return ode_rhs - - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - from grudge.timestep import RK4TimeStepper - stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - logmgr = LogManager("navierstokes-%d.dat" % order, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - from logpyle import LogQuantity - - class ChangeSinceLastStep(LogQuantity): - """Records the change of a variable between a time step and the previous - one""" - - def __init__(self, name="change"): - LogQuantity.__init__(self, name, "1", "Change since last time step") - - self.old_fields = 0 - - def __call__(self): - result = discr.norm(fields - self.old_fields) - self.old_fields = fields - return result - - logmgr.add_quantity(ChangeSinceLastStep()) - - # timestep loop ------------------------------------------------------- - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=200, - #max_steps=500, - logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - if step % 200 == 0: - #if False: - visf = vis.make_file("box-%d-%06d" % (order, step)) - - #rhs_fields = rhs(t, fields) - - vis.add_data(visf, - [ - ("rho", discr.convert_volume( - op.rho(fields), kind="numpy")), - ("e", discr.convert_volume( - op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume( - op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume( - op.u(fields), kind="numpy")), - - # ("rhs_rho", discr.convert_volume( - # op.rho(rhs_fields), kind="numpy")), - # ("rhs_e", discr.convert_volume( - # op.e(rhs_fields), kind="numpy")), - # ("rhs_rho_u", discr.convert_volume( - # op.rho_u(rhs_fields), kind="numpy")), - ], - expressions=[ - ("p", "(0.4)*(e- 0.5*(rho_u*u))"), - ], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - finally: - vis.close() - logmgr.save() - discr.close() - -if __name__ == "__main__": - main() diff --git a/unported-examples/gas_dynamics/euler/sine-wave.py b/unported-examples/gas_dynamics/euler/sine-wave.py deleted file mode 100644 index e5fb0bca2cdfb21e722a2f3904059fec922b084c..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/euler/sine-wave.py +++ /dev/null @@ -1,204 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la - - - - -class SineWave: - def __init__(self): - self.gamma = 1.4 - self.mu = 0 - self.prandtl = 0.72 - self.spec_gas_const = 287.1 - - def __call__(self, t, x_vec): - rho = 2 + numpy.sin(x_vec[0] + x_vec[1] + x_vec[2] - 2 * t) - velocity = numpy.array([1, 1, 0]) - p = 1 - e = p/(self.gamma-1) + rho/2 * numpy.dot(velocity, velocity) - rho_u = rho * velocity[0] - rho_v = rho * velocity[1] - rho_w = rho * velocity[2] - - from grudge.tools import join_fields - return join_fields(rho, e, rho_u, rho_v, rho_w) - - def properties(self): - return(self.gamma, self.mu, self.prandtl, self.spec_gas_const) - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T), - kind=discr.compute_kind) - - def boundary_interpolant(self, t, discr, tag): - return discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T), - tag=tag, kind=discr.compute_kind) - - - - -def main(final_time=1, write_output=False): - from grudge.backends import guess_run_context - rcon = guess_run_context() - - from grudge.tools import EOCRecorder, to_obj_array - eoc_rec = EOCRecorder() - - if rcon.is_head_rank: - from grudge.mesh import make_box_mesh - mesh = make_box_mesh((0,0,0), (10,10,10), max_volume=0.5) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [3, 4, 5]: - discr = rcon.make_discretization(mesh_data, order=order, - default_scalar_type=numpy.float64) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - vis = VtkVisualizer(discr, rcon, "sinewave-%d" % order) - #vis = SiloVisualizer(discr, rcon) - - sinewave = SineWave() - fields = sinewave.volume_interpolant(0, discr) - gamma, mu, prandtl, spec_gas_const = sinewave.properties() - - from grudge.mesh import BTAG_ALL - from grudge.models.gas_dynamics import GasDynamicsOperator - op = GasDynamicsOperator(dimensions=mesh.dimensions, gamma=gamma, mu=mu, - prandtl=prandtl, spec_gas_const=spec_gas_const, - bc_inflow=sinewave, bc_outflow=sinewave, bc_noslip=sinewave, - inflow_tag=BTAG_ALL, source=None) - - euler_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = euler_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - from grudge.timestep import RK4TimeStepper - stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - if write_output: - log_name = ("euler-sinewave-%(order)d-%(els)d.dat" - % {"order":order, "els":len(mesh.elements)}) - else: - log_name = False - logmgr = LogManager(log_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ------------------------------------------------------- - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=final_time, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - #if step % 10 == 0: - if write_output: - visf = vis.make_file("sinewave-%d-%04d" % (order, step)) - - #from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", op.rho(true_fields)), - #("true_e", op.e(true_fields)), - #("true_rho_u", op.rho_u(true_fields)), - #("true_u", op.u(true_fields)), - - #("rhs_rho", op.rho(rhs_fields)), - #("rhs_e", op.e(rhs_fields)), - #("rhs_rho_u", op.rho_u(rhs_fields)), - ], - #expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - #("p", "0.4*(e- 0.5*(rho_u*u))"), - #], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - finally: - vis.close() - logmgr.close() - discr.close() - - true_fields = sinewave.volume_interpolant(t, discr) - eoc_rec.add_data_point(order, discr.norm(fields-true_fields)) - print() - print(eoc_rec.pretty_print("P.Deg.", "L2 Error")) - - - - -if __name__ == "__main__": - main() - - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_euler_sine_wave(): - main(final_time=0.1, write_output=False) - diff --git a/unported-examples/gas_dynamics/euler/sod-2d.py b/unported-examples/gas_dynamics/euler/sod-2d.py deleted file mode 100644 index 5e6ca49c100b0dfa4bcfcc274363be6e7c0b8c0d..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/euler/sod-2d.py +++ /dev/null @@ -1,185 +0,0 @@ -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la - - - - -class Sod: - def __init__(self, gamma): - self.gamma = gamma - self.prandtl = 0.72 - - def __call__(self, t, x_vec): - - from grudge.tools import heaviside - from grudge.tools import heaviside_a - - x_rel = x_vec[0] - y_rel = x_vec[1] - - from math import pi - r = numpy.sqrt(x_rel**2+y_rel**2) - r_shift=r-3.0 - u = 0.0 - v = 0.0 - from numpy import sign - rho = heaviside(-r_shift)+.125*heaviside_a(r_shift,1.0) - e = (1.0/(self.gamma-1.0))*(heaviside(-r_shift)+.1*heaviside_a(r_shift,1.0)) - p = (self.gamma-1.0)*e - - from grudge.tools import join_fields - return join_fields(rho, e, rho*u, rho*v) - - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T), - kind=discr.compute_kind) - - def boundary_interpolant(self, t, discr, tag): - return discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T), - tag=tag, kind=discr.compute_kind) - - - - -def main(): - from grudge.backends import guess_run_context - rcon = guess_run_context() - - from grudge.tools import to_obj_array - - if rcon.is_head_rank: - from grudge.mesh.generator import make_rect_mesh - mesh = make_rect_mesh((-5,-5), (5,5), max_area=0.01) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [1]: - discr = rcon.make_discretization(mesh_data, order=order, - default_scalar_type=numpy.float64) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - vis = VtkVisualizer(discr, rcon, "Sod2D-%d" % order) - #vis = SiloVisualizer(discr, rcon) - - sod_field = Sod(gamma=1.4) - fields = sod_field.volume_interpolant(0, discr) - - from grudge.models.gas_dynamics import GasDynamicsOperator - from grudge.mesh import BTAG_ALL - op = GasDynamicsOperator(dimensions=2, gamma=sod_field.gamma, mu=0.0, - prandtl=sod_field.prandtl, - bc_inflow=sod_field, - bc_outflow=sod_field, - bc_noslip=sod_field, - inflow_tag=BTAG_ALL, - source=None) - - euler_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = euler_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - # limiter setup ------------------------------------------------------------ - from grudge.models.gas_dynamics import SlopeLimiter1NEuler - limiter = SlopeLimiter1NEuler(discr, sod_field.gamma, 2, op) - - # integrator setup--------------------------------------------------------- - from grudge.timestep import SSPRK3TimeStepper, RK4TimeStepper - stepper = SSPRK3TimeStepper(limiter=limiter) - #stepper = SSPRK3TimeStepper() - #stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - logmgr = LogManager("euler-%d.dat" % order, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # filter setup------------------------------------------------------------- - from grudge.discretization import Filter, ExponentialFilterResponseFunction - mode_filter = Filter(discr, - ExponentialFilterResponseFunction(min_amplification=0.9,order=4)) - - # timestep loop ------------------------------------------------------- - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=1.0, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - if step % 5 == 0: - #if False: - visf = vis.make_file("vortex-%d-%04d" % (order, step)) - - #true_fields = vortex.volume_interpolant(t, discr) - - #from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", op.rho(true_fields)), - #("true_e", op.e(true_fields)), - #("true_rho_u", op.rho_u(true_fields)), - #("true_u", op.u(true_fields)), - - #("rhs_rho", op.rho(rhs_fields)), - #("rhs_e", op.e(rhs_fields)), - #("rhs_rho_u", op.rho_u(rhs_fields)), - ], - #expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - #("p", "0.4*(e- 0.5*(rho_u*u))"), - #], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - # fields = limiter(fields) - # fields = mode_filter(fields) - - assert not numpy.isnan(numpy.sum(fields[0])) - finally: - vis.close() - logmgr.close() - discr.close() - - # not solution, just to check against when making code changes - true_fields = sod_field.volume_interpolant(t, discr) - print(discr.norm(fields-true_fields)) - -if __name__ == "__main__": - main() diff --git a/unported-examples/gas_dynamics/euler/vortex-adaptive-grid.py b/unported-examples/gas_dynamics/euler/vortex-adaptive-grid.py deleted file mode 100644 index 6faeafad6cca4b369bcfe37577532365db6a5af3..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/euler/vortex-adaptive-grid.py +++ /dev/null @@ -1,265 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la - - - - -def main(write_output=True): - from pytools import add_python_path_relative_to_script - add_python_path_relative_to_script("..") - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - from grudge.tools import EOCRecorder - eoc_rec = EOCRecorder() - - - if rcon.is_head_rank: - from grudge.mesh.generator import \ - make_rect_mesh, \ - make_centered_regular_rect_mesh - - refine = 4 - mesh = make_centered_regular_rect_mesh((0,-5), (10,5), n=(9,9), - post_refine_factor=refine) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - # a second mesh to regrid to - if rcon.is_head_rank: - from grudge.mesh.generator import \ - make_rect_mesh, \ - make_centered_regular_rect_mesh - - refine = 4 - mesh2 = make_centered_regular_rect_mesh((0,-5), (10,5), n=(8,8), - post_refine_factor=refine) - mesh_data2 = rcon.distribute_mesh(mesh2) - else: - mesh_data2 = rcon.receive_mesh() - - - - for order in [3,4]: - discr = rcon.make_discretization(mesh_data, order=order, - default_scalar_type=numpy.float64, - quad_min_degrees={ - "gasdyn_vol": 3*order, - "gasdyn_face": 3*order, - }) - - discr2 = rcon.make_discretization(mesh_data2, order=order, - default_scalar_type=numpy.float64, - quad_min_degrees={ - "gasdyn_vol": 3*order, - "gasdyn_face": 3*order, - }) - - - from grudge.visualization import SiloVisualizer, VtkVisualizer - vis = VtkVisualizer(discr, rcon, "vortex-%d" % order) - #vis = SiloVisualizer(discr, rcon) - - from gas_dynamics_initials import Vortex - vortex = Vortex() - fields = vortex.volume_interpolant(0, discr) - - from grudge.models.gas_dynamics import GasDynamicsOperator - from grudge.mesh import BTAG_ALL - - op = GasDynamicsOperator(dimensions=2, gamma=vortex.gamma, mu=vortex.mu, - prandtl=vortex.prandtl, spec_gas_const=vortex.spec_gas_const, - bc_inflow=vortex, bc_outflow=vortex, bc_noslip=vortex, - inflow_tag=BTAG_ALL, source=None) - - euler_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = euler_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements for mesh 1 =", len(mesh.elements)) - print("#elements for mesh 2 =", len(mesh2.elements)) - - - # limiter ------------------------------------------------------------ - from grudge.models.gas_dynamics import SlopeLimiter1NEuler - limiter = SlopeLimiter1NEuler(discr, vortex.gamma, 2, op) - - from grudge.timestep import SSPRK3TimeStepper - #stepper = SSPRK3TimeStepper(limiter=limiter) - stepper = SSPRK3TimeStepper() - - #from grudge.timestep import RK4TimeStepper - #stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - if write_output: - log_file_name = "euler-%d.dat" % order - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ------------------------------------------------------- - try: - final_time = 0.2 - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=final_time, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - if step % 10 == 0 and write_output: - #if False: - visf = vis.make_file("vortex-%d-%04d" % (order, step)) - - #true_fields = vortex.volume_interpolant(t, discr) - - from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), - #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), - #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), - #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), - - #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), - #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), - #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), - ], - #expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - #("p", "0.4*(e- 0.5*(rho_u*u))"), - #], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - #fields = limiter(fields) - - #regrid to discr2 at some arbitrary time - if step == 21: - - #get interpolated fields - fields = discr.get_regrid_values(fields, discr2, dtype=None, use_btree=True, thresh=1e-8) - #get new stepper (old one has reference to discr - stepper = SSPRK3TimeStepper() - #new bind - euler_ex = op.bind(discr2) - #new rhs - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = euler_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(t+dt, fields) - #add logmanager - #discr2.add_instrumentation(logmgr) - #new step_it - step_it = times_and_steps( - final_time=final_time, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr2, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - #new visualization - vis.close() - vis = VtkVisualizer(discr2, rcon, "vortexNewGrid-%d" % order) - discr=discr2 - - - - assert not numpy.isnan(numpy.sum(fields[0])) - - true_fields = vortex.volume_interpolant(final_time, discr) - l2_error = discr.norm(fields-true_fields) - l2_error_rho = discr.norm(op.rho(fields)-op.rho(true_fields)) - l2_error_e = discr.norm(op.e(fields)-op.e(true_fields)) - l2_error_rhou = discr.norm(op.rho_u(fields)-op.rho_u(true_fields)) - l2_error_u = discr.norm(op.u(fields)-op.u(true_fields)) - - eoc_rec.add_data_point(order, l2_error) - print() - print(eoc_rec.pretty_print("P.Deg.", "L2 Error")) - - logmgr.set_constant("l2_error", l2_error) - logmgr.set_constant("l2_error_rho", l2_error_rho) - logmgr.set_constant("l2_error_e", l2_error_e) - logmgr.set_constant("l2_error_rhou", l2_error_rhou) - logmgr.set_constant("l2_error_u", l2_error_u) - logmgr.set_constant("refinement", refine) - - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - - - - # after order loop - # assert eoc_rec.estimate_order_of_convergence()[0,1] > 6 - - - - -if __name__ == "__main__": - main() diff --git a/unported-examples/gas_dynamics/euler/vortex-sources.py b/unported-examples/gas_dynamics/euler/vortex-sources.py deleted file mode 100644 index a0f466f8a4dc87793514f3f6ac8428f8337d49e4..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/euler/vortex-sources.py +++ /dev/null @@ -1,341 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la - -# modifies isentropic vortex solution so that rho->Arho, P->A^gamma rho^gamma -# this will be analytic solution if appropriate source terms are added -# to the RHS. coded for vel_x=1, vel_y=0 - - -class Vortex: - def __init__(self, beta, gamma, center, velocity, densityA): - self.beta = beta - self.gamma = gamma - self.center = numpy.asarray(center) - self.velocity = numpy.asarray(velocity) - self.densityA = densityA - - def __call__(self, t, x_vec): - vortex_loc = self.center + t*self.velocity - - # coordinates relative to vortex center - x_rel = x_vec[0] - vortex_loc[0] - y_rel = x_vec[1] - vortex_loc[1] - - # Y.C. Zhou, G.W. Wei / Journal of Computational Physics 189 (2003) 159 - # also JSH/TW Nodal DG Methods, p. 209 - - from math import pi - r = numpy.sqrt(x_rel**2+y_rel**2) - expterm = self.beta*numpy.exp(1-r**2) - u = self.velocity[0] - expterm*y_rel/(2*pi) - v = self.velocity[1] + expterm*x_rel/(2*pi) - rho = self.densityA*(1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) - p = rho**self.gamma - - e = p/(self.gamma-1) + rho/2*(u**2+v**2) - - from grudge.tools import join_fields - return join_fields(rho, e, rho*u, rho*v) - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T - .astype(discr.default_scalar_type)), - kind=discr.compute_kind) - - def boundary_interpolant(self, t, discr, tag): - return discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T - .astype(discr.default_scalar_type)), - tag=tag, kind=discr.compute_kind) - - - -class SourceTerms: - def __init__(self, beta, gamma, center, velocity, densityA): - self.beta = beta - self.gamma = gamma - self.center = numpy.asarray(center) - self.velocity = numpy.asarray(velocity) - self.densityA = densityA - - - - def __call__(self,t,x_vec,q): - - vortex_loc = self.center + t*self.velocity - - # coordinates relative to vortex center - x_rel = x_vec[0] - vortex_loc[0] - y_rel = x_vec[1] - vortex_loc[1] - - # sources written in terms of A=1.0 solution - # (standard isentropic vortex) - - from math import pi - r = numpy.sqrt(x_rel**2+y_rel**2) - expterm = self.beta*numpy.exp(1-r**2) - u = self.velocity[0] - expterm*y_rel/(2*pi) - v = self.velocity[1] + expterm*x_rel/(2*pi) - rho = (1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) - p = rho**self.gamma - - #computed necessary derivatives - expterm_t = 2*expterm*x_rel - expterm_x = -2*expterm*x_rel - expterm_y = -2*expterm*y_rel - u_x = -expterm*y_rel/(2*pi)*(-2*x_rel) - v_y = expterm*x_rel/(2*pi)*(-2*y_rel) - - #derivatives for rho (A=1) - facG=self.gamma-1 - rho_t = (1/facG)*(1-(facG)/(16*self.gamma*pi**2)*expterm**2)**(1/facG-1)* \ - (-facG/(16*self.gamma*pi**2)*2*expterm*expterm_t) - rho_x = (1/facG)*(1-(facG)/(16*self.gamma*pi**2)*expterm**2)**(1/facG-1)* \ - (-facG/(16*self.gamma*pi**2)*2*expterm*expterm_x) - rho_y = (1/facG)*(1-(facG)/(16*self.gamma*pi**2)*expterm**2)**(1/facG-1)* \ - (-facG/(16*self.gamma*pi**2)*2*expterm*expterm_y) - - #derivatives for rho (A=1) to the power of gamma - rho_gamma_t = self.gamma*rho**(self.gamma-1)*rho_t - rho_gamma_x = self.gamma*rho**(self.gamma-1)*rho_x - rho_gamma_y = self.gamma*rho**(self.gamma-1)*rho_y - - - factorA=self.densityA**self.gamma-self.densityA - #construct source terms - source_rho = x_vec[0]-x_vec[0] - source_e = (factorA/(self.gamma-1))*(rho_gamma_t + self.gamma*(u_x*rho**self.gamma+u*rho_gamma_x)+ \ - self.gamma*(v_y*rho**self.gamma+v*rho_gamma_y)) - source_rhou = factorA*rho_gamma_x - source_rhov = factorA*rho_gamma_y - - from grudge.tools import join_fields - return join_fields(source_rho, source_e, source_rhou, source_rhov, x_vec[0]-x_vec[0]) - - def volume_interpolant(self,t,q,discr): - return discr.convert_volume( - self(t,discr.nodes.T,q), - kind=discr.compute_kind) - - -def main(write_output=True): - from grudge.backends import guess_run_context - rcon = guess_run_context( - #["cuda"] - ) - - gamma = 1.4 - - # at A=1 we have case of isentropic vortex, source terms - # arise for other values - densityA = 2.0 - - from grudge.tools import EOCRecorder, to_obj_array - eoc_rec = EOCRecorder() - - if rcon.is_head_rank: - from grudge.mesh import \ - make_rect_mesh, \ - make_centered_regular_rect_mesh - - refine = 1 - mesh = make_centered_regular_rect_mesh((0,-5), (10,5), n=(9,9), - post_refine_factor=refine) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [4,5]: - discr = rcon.make_discretization(mesh_data, order=order, - debug=[#"cuda_no_plan", - #"print_op_code" - ], - default_scalar_type=numpy.float64) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - #vis = VtkVisualizer(discr, rcon, "vortex-%d" % order) - vis = SiloVisualizer(discr, rcon) - - vortex = Vortex(beta=5, gamma=gamma, - center=[5,0], - velocity=[1,0], densityA=densityA) - fields = vortex.volume_interpolant(0, discr) - sources=SourceTerms(beta=5, gamma=gamma, - center=[5,0], - velocity=[1,0], densityA=densityA) - - from grudge.models.gas_dynamics import ( - GasDynamicsOperator, GammaLawEOS) - from grudge.mesh import BTAG_ALL - - op = GasDynamicsOperator(dimensions=2, - mu=0.0, prandtl=0.72, spec_gas_const=287.1, - equation_of_state=GammaLawEOS(vortex.gamma), - bc_inflow=vortex, bc_outflow=vortex, bc_noslip=vortex, - inflow_tag=BTAG_ALL, source=sources) - - euler_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = euler_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - # limiter setup ------------------------------------------------------- - from grudge.models.gas_dynamics import SlopeLimiter1NEuler - limiter = SlopeLimiter1NEuler(discr, gamma, 2, op) - - # time stepper -------------------------------------------------------- - from grudge.timestep import SSPRK3TimeStepper, RK4TimeStepper - #stepper = SSPRK3TimeStepper(limiter=limiter) - #stepper = SSPRK3TimeStepper() - stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - if write_output: - log_file_name = "euler-%d.dat" % order - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ------------------------------------------------------- - t = 0 - - #fields = limiter(fields) - - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=.1, - #max_steps=500, - logmgr=logmgr, - max_dt_getter=lambda t: 0.4*op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - if step % 1 == 0 and write_output: - #if False: - visf = vis.make_file("vortex-%d-%04d" % (order, step)) - - true_fields = vortex.volume_interpolant(t, discr) - - #rhs_fields = rhs(t, fields) - - from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), - #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), - #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), - #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), - - #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), - #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), - #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), - ], - expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - ("p", "0.4*(e- 0.5*(rho_u*u))"), - ], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - true_fields = vortex.volume_interpolant(t, discr) - l2_error = discr.norm(fields-true_fields) - l2_error_rho = discr.norm(op.rho(fields)-op.rho(true_fields)) - l2_error_e = discr.norm(op.e(fields)-op.e(true_fields)) - l2_error_rhou = discr.norm(op.rho_u(fields)-op.rho_u(true_fields)) - l2_error_u = discr.norm(op.u(fields)-op.u(true_fields)) - - eoc_rec.add_data_point(order, l2_error_rho) - print() - print(eoc_rec.pretty_print("P.Deg.", "L2 Error")) - - logmgr.set_constant("l2_error", l2_error) - logmgr.set_constant("l2_error_rho", l2_error_rho) - logmgr.set_constant("l2_error_e", l2_error_e) - logmgr.set_constant("l2_error_rhou", l2_error_rhou) - logmgr.set_constant("l2_error_u", l2_error_u) - logmgr.set_constant("refinement", refine) - - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - - # after order loop - #assert eoc_rec.estimate_order_of_convergence()[0,1] > 6 - - - - -if __name__ == "__main__": - main() - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_euler_vortex(): - main(write_output=False) diff --git a/unported-examples/gas_dynamics/euler/vortex.py b/unported-examples/gas_dynamics/euler/vortex.py deleted file mode 100644 index 2129f88272f5421dd576e211a1d57ed8bc09ca35..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/euler/vortex.py +++ /dev/null @@ -1,220 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy - - - - -def main(write_output=True): - from pytools import add_python_path_relative_to_script - add_python_path_relative_to_script("..") - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - from grudge.tools import EOCRecorder - eoc_rec = EOCRecorder() - - if rcon.is_head_rank: - from grudge.mesh.generator import \ - make_rect_mesh, \ - make_centered_regular_rect_mesh - - refine = 4 - mesh = make_centered_regular_rect_mesh((0,-5), (10,5), n=(9,9), - post_refine_factor=refine) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [3, 4, 5]: - from gas_dynamics_initials import Vortex - flow = Vortex() - - from grudge.models.gas_dynamics import ( - GasDynamicsOperator, PolytropeEOS, GammaLawEOS) - - from grudge.mesh import BTAG_ALL - # works equally well for GammaLawEOS - op = GasDynamicsOperator(dimensions=2, mu=flow.mu, - prandtl=flow.prandtl, spec_gas_const=flow.spec_gas_const, - equation_of_state=PolytropeEOS(flow.gamma), - bc_inflow=flow, bc_outflow=flow, bc_noslip=flow, - inflow_tag=BTAG_ALL, source=None) - - discr = rcon.make_discretization(mesh_data, order=order, - default_scalar_type=numpy.float64, - quad_min_degrees={ - "gasdyn_vol": 3*order, - "gasdyn_face": 3*order, - }, - tune_for=op.sym_operator(), - debug=["cuda_no_plan"]) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - vis = VtkVisualizer(discr, rcon, "vortex-%d" % order) - #vis = SiloVisualizer(discr, rcon) - - fields = flow.volume_interpolant(0, discr) - - euler_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = euler_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - - # limiter ------------------------------------------------------------ - from grudge.models.gas_dynamics import SlopeLimiter1NEuler - limiter = SlopeLimiter1NEuler(discr, flow.gamma, 2, op) - - from grudge.timestep.runge_kutta import SSP3TimeStepper - #stepper = SSP3TimeStepper(limiter=limiter) - stepper = SSP3TimeStepper( - vector_primitive_factory=discr.get_vector_primitive_factory()) - - #from grudge.timestep import RK4TimeStepper - #stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - if write_output: - log_file_name = "euler-%d.dat" % order - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ------------------------------------------------------- - try: - final_time = flow.final_time - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=final_time, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - print("run until t=%g" % final_time) - for step, t, dt in step_it: - if step % 10 == 0 and write_output: - #if False: - visf = vis.make_file("vortex-%d-%04d" % (order, step)) - - #true_fields = vortex.volume_interpolant(t, discr) - - from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), - #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), - #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), - #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), - - #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), - #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), - #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), - ], - #expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - #("p", "0.4*(e- 0.5*(rho_u*u))"), - #], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - #fields = limiter(fields) - - assert not numpy.isnan(numpy.sum(fields[0])) - - true_fields = flow.volume_interpolant(final_time, discr) - l2_error = discr.norm(fields-true_fields) - l2_error_rho = discr.norm(op.rho(fields)-op.rho(true_fields)) - l2_error_e = discr.norm(op.e(fields)-op.e(true_fields)) - l2_error_rhou = discr.norm(op.rho_u(fields)-op.rho_u(true_fields)) - l2_error_u = discr.norm(op.u(fields)-op.u(true_fields)) - - eoc_rec.add_data_point(order, l2_error) - print() - print(eoc_rec.pretty_print("P.Deg.", "L2 Error")) - - logmgr.set_constant("l2_error", l2_error) - logmgr.set_constant("l2_error_rho", l2_error_rho) - logmgr.set_constant("l2_error_e", l2_error_e) - logmgr.set_constant("l2_error_rhou", l2_error_rhou) - logmgr.set_constant("l2_error_u", l2_error_u) - logmgr.set_constant("refinement", refine) - - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - - # after order loop - assert eoc_rec.estimate_order_of_convergence()[0,1] > 6 - - - - -if __name__ == "__main__": - main() - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_euler_vortex(): - main(write_output=False) diff --git a/unported-examples/gas_dynamics/gas_dynamics_initials.py b/unported-examples/gas_dynamics/gas_dynamics_initials.py deleted file mode 100644 index 48057a865fda5698982602899ac3a6100926fb38..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/gas_dynamics_initials.py +++ /dev/null @@ -1,229 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -import numpy -import numpy.linalg as la -from six.moves import range - - - - -class UniformMachFlow: - def __init__(self, mach=0.1, p=1, rho=1, reynolds=100, - gamma=1.4, prandtl=0.72, char_length=1, spec_gas_const=287.1, - angle_of_attack=None, direction=None, gaussian_pulse_at=None, - pulse_magnitude=0.1): - """ - :param direction: is a vector indicating the direction of the - flow. Only one of angle_of_attack and direction may be - specified. Only the direction, not the magnitude, of - direction is taken into account. - - :param angle_of_attack: if not None, specifies the angle of - the flow along the Y axis, where the flow is - directed along the X axis. - """ - if angle_of_attack is not None and direction is not None: - raise ValueError("Only one of angle_of_attack and " - "direction may be specified.") - - if angle_of_attack is None and direction is None: - angle_of_attack = 0 - - if direction is not None: - self.direction = direction/la.norm(direction) - else: - self.direction = None - - self.mach = mach - self.p = p - self.rho = rho - - self.gamma = gamma - self.prandtl = prandtl - self.reynolds = reynolds - self.length = char_length - self.spec_gas_const = spec_gas_const - - self.angle_of_attack = angle_of_attack - - self.gaussian_pulse_at = gaussian_pulse_at - self.pulse_magnitude = pulse_magnitude - - self.c = (self.gamma * p / rho)**0.5 - u = self.velocity = mach * self.c - self.e = p / (self.gamma - 1) + rho / 2 * u**2 - - if numpy.isinf(self.reynolds): - self.mu = 0 - else: - self.mu = u * self.length * rho / self.reynolds - - def direction_vector(self, dimensions): - # this must be done here because dimensions is not known above - if self.direction is None: - assert self.angle_of_attack is not None - direction = numpy.zeros(dimensions, dtype=numpy.float64) - direction[0] = numpy.cos( - self.angle_of_attack / 180. * numpy.pi) - direction[1] = numpy.sin( - self.angle_of_attack / 180. * numpy.pi) - return direction - else: - return self.direction - - def __call__(self, t, x_vec): - ones = numpy.ones_like(x_vec[0]) - rho_field = ones*self.rho - - if self.gaussian_pulse_at is not None: - rel_to_pulse = [x_vec[i] - self.gaussian_pulse_at[i] - for i in range(len(x_vec))] - rho_field += self.pulse_magnitude * self.rho * numpy.exp( - - sum(rtp_i**2 for rtp_i in rel_to_pulse)/2) - - direction = self.direction_vector(x_vec.shape[0]) - - from grudge.tools import make_obj_array - u_field = make_obj_array([ones*self.velocity*dir_i - for dir_i in direction]) - - from grudge.tools import join_fields - return join_fields(rho_field, self.e*ones, self.rho*u_field) - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T), - kind=discr.compute_kind, - dtype=discr.default_scalar_type) - - def boundary_interpolant(self, t, discr, tag): - return discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T), - tag=tag, kind=discr.compute_kind, - dtype=discr.default_scalar_type) - -class Vortex: - def __init__(self): - self.beta = 5 - self.gamma = 1.4 - self.center = numpy.array([5, 0]) - self.velocity = numpy.array([1, 0]) - - self.mu = 0 - self.prandtl = 0.72 - self.spec_gas_const = 287.1 - - def __call__(self, t, x_vec): - vortex_loc = self.center + t*self.velocity - - # coordinates relative to vortex center - x_rel = x_vec[0] - vortex_loc[0] - y_rel = x_vec[1] - vortex_loc[1] - - # Y.C. Zhou, G.W. Wei / Journal of Computational Physics 189 (2003) 159 - # also JSH/TW Nodal DG Methods, p. 209 - - from math import pi - r = numpy.sqrt(x_rel**2+y_rel**2) - expterm = self.beta*numpy.exp(1-r**2) - u = self.velocity[0] - expterm*y_rel/(2*pi) - v = self.velocity[1] + expterm*x_rel/(2*pi) - rho = (1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) - p = rho**self.gamma - - e = p/(self.gamma-1) + rho/2*(u**2+v**2) - - from grudge.tools import join_fields - return join_fields(rho, e, rho*u, rho*v) - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T - .astype(discr.default_scalar_type)), - kind=discr.compute_kind) - - def boundary_interpolant(self, t, discr, tag): - return discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T - .astype(discr.default_scalar_type)), - tag=tag, kind=discr.compute_kind) - - - - - - - -class Vortex: - def __init__(self): - self.beta = 5 - self.gamma = 1.4 - self.center = numpy.array([5, 0]) - self.velocity = numpy.array([1, 0]) - self.final_time = 0.5 - - self.mu = 0 - self.prandtl = 0.72 - self.spec_gas_const = 287.1 - - def __call__(self, t, x_vec): - vortex_loc = self.center + t*self.velocity - - # coordinates relative to vortex center - x_rel = x_vec[0] - vortex_loc[0] - y_rel = x_vec[1] - vortex_loc[1] - - # Y.C. Zhou, G.W. Wei / Journal of Computational Physics 189 (2003) 159 - # also JSH/TW Nodal DG Methods, p. 209 - - from math import pi - r = numpy.sqrt(x_rel**2+y_rel**2) - expterm = self.beta*numpy.exp(1-r**2) - u = self.velocity[0] - expterm*y_rel/(2*pi) - v = self.velocity[1] + expterm*x_rel/(2*pi) - rho = (1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) - p = rho**self.gamma - - e = p/(self.gamma-1) + rho/2*(u**2+v**2) - - from grudge.tools import join_fields - return join_fields(rho, e, rho*u, rho*v) - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T - .astype(discr.default_scalar_type)), - kind=discr.compute_kind) - - def boundary_interpolant(self, t, discr, tag): - return discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T - .astype(discr.default_scalar_type)), - tag=tag, kind=discr.compute_kind) - - - - diff --git a/unported-examples/gas_dynamics/lbm-simple.py b/unported-examples/gas_dynamics/lbm-simple.py deleted file mode 100644 index b6eb94c6980d613adcd1522254a0ad9fc802464c..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/lbm-simple.py +++ /dev/null @@ -1,162 +0,0 @@ -__copyright__ = "Copyright (C) 2011 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy as np -import numpy.linalg as la -from six.moves import range - - - - -def main(write_output=True, dtype=np.float32): - from grudge.backends import guess_run_context - rcon = guess_run_context() - - from grudge.mesh.generator import make_rect_mesh - if rcon.is_head_rank: - h_fac = 1 - mesh = make_rect_mesh(a=(0,0),b=(1,1), max_area=h_fac**2*1e-4, - periodicity=(True,True), - subdivisions=(int(70/h_fac), int(70/h_fac))) - - from grudge.models.gas_dynamics.lbm import \ - D2Q9LBMMethod, LatticeBoltzmannOperator - - op = LatticeBoltzmannOperator( - D2Q9LBMMethod(), lbm_delta_t=0.001, nu=1e-4) - - if rcon.is_head_rank: - print("%d elements" % len(mesh.elements)) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - discr = rcon.make_discretization(mesh_data, order=3, - default_scalar_type=dtype, - debug=["cuda_no_plan"]) - from grudge.timestep.runge_kutta import LSRK4TimeStepper - stepper = LSRK4TimeStepper(dtype=dtype, - #vector_primitive_factory=discr.get_vector_primitive_factory() - ) - - from grudge.visualization import VtkVisualizer - if write_output: - vis = VtkVisualizer(discr, rcon, "fld") - - from grudge.data import CompiledExpressionData - def ic_expr(t, x, fields): - from grudge.symbolic import FunctionSymbol - from pymbolic.primitives import IfPositive - from pytools.obj_array import make_obj_array - - tanh = FunctionSymbol("tanh") - sin = FunctionSymbol("sin") - - rho = 1 - u0 = 0.05 - w = 0.05 - delta = 0.05 - - from grudge.symbolic.primitives import make_common_subexpression as cse - u = cse(make_obj_array([ - IfPositive(x[1]-1/2, - u0*tanh(4*(3/4-x[1])/w), - u0*tanh(4*(x[1]-1/4)/w)), - u0*delta*sin(2*np.pi*(x[0]+1/4))]), - "u") - - return make_obj_array([ - op.method.f_equilibrium(rho, alpha, u) - for alpha in range(len(op.method)) - ]) - - - # timestep loop ----------------------------------------------------------- - stream_rhs = op.bind_rhs(discr) - collision_update = op.bind(discr, op.collision_update) - get_rho = op.bind(discr, op.rho) - get_rho_u = op.bind(discr, op.rho_u) - - - f_bar = CompiledExpressionData(ic_expr).volume_interpolant(0, discr) - - from grudge.discretization import ExponentialFilterResponseFunction - from grudge.symbolic.operators import FilterOperator - mode_filter = FilterOperator( - ExponentialFilterResponseFunction(min_amplification=0.9, order=4))\ - .bind(discr) - - final_time = 1000 - try: - lbm_dt = op.lbm_delta_t - dg_dt = op.estimate_timestep(discr, stepper=stepper) - print(dg_dt) - - dg_steps_per_lbm_step = int(np.ceil(lbm_dt / dg_dt)) - dg_dt = lbm_dt / dg_steps_per_lbm_step - - lbm_steps = int(final_time // op.lbm_delta_t) - for step in range(lbm_steps): - t = step*lbm_dt - - if step % 100 == 0 and write_output: - visf = vis.make_file("fld-%04d" % step) - - rho = get_rho(f_bar) - rho_u = get_rho_u(f_bar) - vis.add_data(visf, - [ ("fbar%d" %i, - discr.convert_volume(f_bar_i, "numpy")) for i, f_bar_i in enumerate(f_bar)]+ - [ - ("rho", discr.convert_volume(rho, "numpy")), - ("rho_u", discr.convert_volume(rho_u, "numpy")), - ], - time=t, - step=step) - visf.close() - - print("step=%d, t=%f" % (step, t)) - - f_bar = collision_update(f_bar) - - for substep in range(dg_steps_per_lbm_step): - f_bar = stepper(f_bar, t + substep*dg_dt, dg_dt, stream_rhs) - - #f_bar = mode_filter(f_bar) - - finally: - if write_output: - vis.close() - - discr.close() - - - - -if __name__ == "__main__": - main(True) diff --git a/unported-examples/gas_dynamics/naca.py b/unported-examples/gas_dynamics/naca.py deleted file mode 100644 index 0c200c04bbda31b76ac7548de55f614546fd5054..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/naca.py +++ /dev/null @@ -1,275 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la -from six.moves import range - - - - -def make_nacamesh(): - def round_trip_connect(seq): - result = [] - for i in range(len(seq)): - result.append((i, (i+1)%len(seq))) - return result - - pt_back = numpy.array([1,0]) - - #def max_area(pt): - #max_area_front = 1e-2*la.norm(pt)**2 + 1e-5 - #max_area_back = 1e-2*la.norm(pt-pt_back)**2 + 1e-4 - #return min(max_area_front, max_area_back) - - def max_area(pt): - x = pt[0] - - if x < 0: - return 1e-2*la.norm(pt)**2 + 1e-5 - elif x > 1: - return 1e-2*la.norm(pt-pt_back)**2 + 1e-5 - else: - return 1e-2*pt[1]**2 + 1e-5 - - def needs_refinement(vertices, area): - barycenter = sum(numpy.array(v) for v in vertices)/3 - return bool(area > max_area(barycenter)) - - from meshpy.naca import get_naca_points - points = get_naca_points(naca_digits="2412", number_of_points=80) - - from meshpy.geometry import GeometryBuilder, Marker - from meshpy.triangle import write_gnuplot_mesh - - profile_marker = Marker.FIRST_USER_MARKER - builder = GeometryBuilder() - builder.add_geometry(points=points, - facets=round_trip_connect(points), - facet_markers=profile_marker) - builder.wrap_in_box(4, (10, 8)) - - from meshpy.triangle import MeshInfo, build - mi = MeshInfo() - builder.set(mi) - mi.set_holes([builder.center()]) - - mesh = build(mi, refinement_func=needs_refinement, - #allow_boundary_steiner=False, - generate_faces=True) - - write_gnuplot_mesh("mesh.dat", mesh) - - print("%d elements" % len(mesh.elements)) - - fvi2fm = mesh.face_vertex_indices_to_face_marker - - face_marker_to_tag = { - profile_marker: "noslip", - Marker.MINUS_X: "inflow", - Marker.PLUS_X: "outflow", - Marker.MINUS_Y: "inflow", - Marker.PLUS_Y: "inflow" - #Marker.MINUS_Y: "minus_y", - #Marker.PLUS_Y: "plus_y" - } - - def bdry_tagger(fvi, el, fn, all_v): - face_marker = fvi2fm[fvi] - return [face_marker_to_tag[face_marker]] - - from grudge.mesh import make_conformal_mesh_ext - - vertices = numpy.asarray(mesh.points, order="C") - from grudge.mesh.element import Triangle - return make_conformal_mesh_ext( - vertices, - [Triangle(i, el_idx, vertices) - for i, el_idx in enumerate(mesh.elements)], - bdry_tagger, - #periodicity=[None, ("minus_y", "plus_y")] - ) - - - - -def main(): - from grudge.backends import guess_run_context - rcon = guess_run_context() - - if rcon.is_head_rank: - mesh = make_nacamesh() - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - from pytools import add_python_path_relative_to_script - add_python_path_relative_to_script("..") - - for order in [4]: - from gas_dynamics_initials import UniformMachFlow - uniform_flow = UniformMachFlow() - - from grudge.models.gas_dynamics import GasDynamicsOperator, GammaLawEOS - op = GasDynamicsOperator(dimensions=2, - equation_of_state=GammaLawEOS(uniform_flow.gamma), - prandtl=uniform_flow.prandtl, - spec_gas_const=uniform_flow.spec_gas_const, mu=uniform_flow.mu, - bc_inflow=uniform_flow, bc_outflow=uniform_flow, bc_noslip=uniform_flow, - inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") - - discr = rcon.make_discretization(mesh_data, order=order, - debug=[ - "cuda_no_plan", - #"cuda_dump_kernels", - #"dump_optemplate_stages", - #"dump_dataflow_graph", - #"print_op_code" - ], - default_scalar_type=numpy.float32, - tune_for=op.sym_operator()) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) - vis = SiloVisualizer(discr, rcon) - - fields = uniform_flow.volume_interpolant(0, discr) - - navierstokes_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = navierstokes_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - from grudge.timestep.runge_kutta import \ - ODE23TimeStepper, LSRK4TimeStepper - stepper = ODE23TimeStepper(dtype=discr.default_scalar_type, - rtol=1e-6, - vector_primitive_factory=discr.get_vector_primitive_factory()) - #stepper = LSRK4TimeStepper(dtype=discr.default_scalar_type) - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - logmgr = LogManager("cns-naca-%d.dat" % order, "w", rcon.communicator) - - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - from logpyle import LogQuantity - class ChangeSinceLastStep(LogQuantity): - """Records the change of a variable between a time step and the previous - one""" - - def __init__(self, name="change"): - LogQuantity.__init__(self, name, "1", "Change since last time step") - - self.old_fields = 0 - - def __call__(self): - result = discr.norm(fields - self.old_fields) - self.old_fields = fields - return result - - #logmgr.add_quantity(ChangeSinceLastStep()) - - # filter setup------------------------------------------------------------- - from grudge.discretization import Filter, ExponentialFilterResponseFunction - mode_filter = Filter(discr, - ExponentialFilterResponseFunction(min_amplification=0.9,order=4)) - # timestep loop ------------------------------------------------------- - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=200, - #max_steps=500, - logmgr=logmgr, - max_dt_getter=lambda t: next_dt, - taken_dt_getter=lambda: taken_dt) - - model_stepper = LSRK4TimeStepper() - next_dt = op.estimate_timestep(discr, - stepper=model_stepper, t=0, - max_eigenvalue=max_eigval[0]) - - for step, t, dt in step_it: - if step % 10 == 0: - visf = vis.make_file("naca-%d-%06d" % (order, step)) - - from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", op.rho(true_fields)), - #("true_e", op.e(true_fields)), - #("true_rho_u", op.rho_u(true_fields)), - #("true_u", op.u(true_fields)), - - #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), - #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), - #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), - ], - expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - ("p", "(0.4)*(e- 0.5*(rho_u*u))"), - ], - time=t, step=step - ) - visf.close() - - fields, t, taken_dt, next_dt = stepper(fields, t, dt, rhs) - fields = mode_filter(fields) - - finally: - vis.close() - logmgr.save() - discr.close() - -if __name__ == "__main__": - main() diff --git a/unported-examples/gas_dynamics/navierstokes/shearflow.py b/unported-examples/gas_dynamics/navierstokes/shearflow.py deleted file mode 100644 index 6c762e1239045cefc8fc6b711f6c3bef423d4eb6..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/navierstokes/shearflow.py +++ /dev/null @@ -1,205 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la - - - - -class SteadyShearFlow: - def __init__(self): - self.gamma = 1.5 - self.mu = 0.01 - self.prandtl = 0.72 - self.spec_gas_const = 287.1 - - def __call__(self, t, x_vec): - # JSH/TW Nodal DG Methods, p.326 - - rho = numpy.ones_like(x_vec[0]) - rho_u = x_vec[1] * x_vec[1] - rho_v = numpy.zeros_like(x_vec[0]) - e = (2 * self.mu * x_vec[0] + 10) / (self.gamma - 1) + x_vec[1]**4 / 2 - - from grudge.tools import join_fields - return join_fields(rho, e, rho_u, rho_v) - - def properties(self): - return(self.gamma, self.mu, self.prandtl, self.spec_gas_const) - - def volume_interpolant(self, t, discr): - return discr.convert_volume( - self(t, discr.nodes.T - .astype(discr.default_scalar_type)), - kind=discr.compute_kind) - - def boundary_interpolant(self, t, discr, tag): - result = discr.convert_boundary( - self(t, discr.get_boundary(tag).nodes.T - .astype(discr.default_scalar_type)), - tag=tag, kind=discr.compute_kind) - return result - - - - -def main(): - from grudge.backends import guess_run_context - rcon = guess_run_context( - #["cuda"] - ) - - from grudge.tools import EOCRecorder, to_obj_array - eoc_rec = EOCRecorder() - - def boundary_tagger(vertices, el, face_nr, all_v): - return ["inflow"] - - if rcon.is_head_rank: - from grudge.mesh import make_rect_mesh, \ - make_centered_regular_rect_mesh - #mesh = make_rect_mesh((0,0), (10,1), max_area=0.01) - refine = 1 - mesh = make_centered_regular_rect_mesh((0,0), (10,1), n=(20,4), - #periodicity=(True, False), - post_refine_factor=refine, - boundary_tagger=boundary_tagger) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [3]: - discr = rcon.make_discretization(mesh_data, order=order, - default_scalar_type=numpy.float64) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) - vis = SiloVisualizer(discr, rcon) - - shearflow = SteadyShearFlow() - fields = shearflow.volume_interpolant(0, discr) - gamma, mu, prandtl, spec_gas_const = shearflow.properties() - - from grudge.models.gas_dynamics import GasDynamicsOperator - op = GasDynamicsOperator(dimensions=2, gamma=gamma, mu=mu, - prandtl=prandtl, spec_gas_const=spec_gas_const, - bc_inflow=shearflow, bc_outflow=shearflow, bc_noslip=shearflow, - inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") - - navierstokes_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = navierstokes_ex(t, q) - max_eigval[0] = speed - return ode_rhs - - # needed to get first estimate of maximum eigenvalue - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - from grudge.timestep import RK4TimeStepper - stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - logmgr = LogManager("navierstokes-cpu-%d-%d.dat" % (order, refine), - "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ------------------------------------------------------- - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=0.3, - #max_steps=500, - logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - if step % 10 == 0: - #if False: - visf = vis.make_file("shearflow-%d-%04d" % (order, step)) - - #true_fields = shearflow.volume_interpolant(t, discr) - - from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), - #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), - #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), - #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), - ], - expressions=[ - #("diff_rho", "rho-true_rho"), - #("diff_e", "e-true_e"), - #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), - - ("p", "0.4*(e- 0.5*(rho_u*u))"), - ], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - true_fields = shearflow.volume_interpolant(t, discr) - l2_error = discr.norm(op.u(fields)-op.u(true_fields)) - eoc_rec.add_data_point(order, l2_error) - print() - print(eoc_rec.pretty_print("P.Deg.", "L2 Error")) - - logmgr.set_constant("l2_error", l2_error) - - finally: - vis.close() - logmgr.save() - discr.close() - -if __name__ == "__main__": - main() diff --git a/unported-examples/gas_dynamics/square.py b/unported-examples/gas_dynamics/square.py deleted file mode 100644 index 632cfb7293ba83d2c54cef97df3c1d449a1baf16..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/square.py +++ /dev/null @@ -1,288 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la -from six.moves import range - - - - -def make_squaremesh(): - def round_trip_connect(seq): - result = [] - for i in range(len(seq)): - result.append((i, (i+1)%len(seq))) - return result - - def needs_refinement(vertices, area): - x = sum(numpy.array(v) for v in vertices)/3 - - max_area_volume = 0.7e-2 + 0.03*(0.05*x[1]**2 + 0.3*min(x[0]+1,0)**2) - - max_area_corners = 1e-3 + 0.001*max( - la.norm(x-corner)**4 for corner in obstacle_corners) - - return bool(area > 2.5*min(max_area_volume, max_area_corners)) - - from meshpy.geometry import make_box - points, facets, _, _ = make_box((-0.5,-0.5), (0.5,0.5)) - obstacle_corners = points[:] - - from meshpy.geometry import GeometryBuilder, Marker - - profile_marker = Marker.FIRST_USER_MARKER - builder = GeometryBuilder() - builder.add_geometry(points=points, facets=facets, - facet_markers=profile_marker) - - points, facets, _, facet_markers = make_box((-16, -22), (25, 22)) - builder.add_geometry(points=points, facets=facets, - facet_markers=facet_markers) - - from meshpy.triangle import MeshInfo, build - mi = MeshInfo() - builder.set(mi) - mi.set_holes([(0,0)]) - - mesh = build(mi, refinement_func=needs_refinement, - allow_boundary_steiner=True, - generate_faces=True) - - print("%d elements" % len(mesh.elements)) - - from meshpy.triangle import write_gnuplot_mesh - write_gnuplot_mesh("mesh.dat", mesh) - - fvi2fm = mesh.face_vertex_indices_to_face_marker - - face_marker_to_tag = { - profile_marker: "noslip", - Marker.MINUS_X: "inflow", - Marker.PLUS_X: "outflow", - Marker.MINUS_Y: "inflow", - Marker.PLUS_Y: "inflow" - } - - def bdry_tagger(fvi, el, fn, all_v): - face_marker = fvi2fm[fvi] - return [face_marker_to_tag[face_marker]] - - from grudge.mesh import make_conformal_mesh_ext - vertices = numpy.asarray(mesh.points, dtype=float, order="C") - from grudge.mesh.element import Triangle - return make_conformal_mesh_ext( - vertices, - [Triangle(i, el_idx, vertices) - for i, el_idx in enumerate(mesh.elements)], - bdry_tagger) - - - - -def main(): - import logging - logging.basicConfig(level=logging.INFO) - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - if rcon.is_head_rank: - if True: - mesh = make_squaremesh() - else: - from grudge.mesh import make_rect_mesh - mesh = make_rect_mesh( - boundary_tagger=lambda fvi, el, fn, all_v: ["inflow"], - max_area=0.1) - - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - from pytools import add_python_path_relative_to_script - add_python_path_relative_to_script(".") - - for order in [3]: - from gas_dynamics_initials import UniformMachFlow - square = UniformMachFlow(gaussian_pulse_at=numpy.array([-2, 2]), - pulse_magnitude=0.003) - - from grudge.models.gas_dynamics import ( - GasDynamicsOperator, - GammaLawEOS) - - op = GasDynamicsOperator(dimensions=2, - equation_of_state=GammaLawEOS(square.gamma), mu=square.mu, - prandtl=square.prandtl, spec_gas_const=square.spec_gas_const, - bc_inflow=square, bc_outflow=square, bc_noslip=square, - inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") - - discr = rcon.make_discretization(mesh_data, order=order, - debug=["cuda_no_plan", - "cuda_dump_kernels", - #"dump_dataflow_graph", - #"dump_optemplate_stages", - #"dump_dataflow_graph", - #"dump_op_code" - #"cuda_no_plan_el_local" - ], - default_scalar_type=numpy.float64, - tune_for=op.sym_operator(), - quad_min_degrees={ - "gasdyn_vol": 3*order, - "gasdyn_face": 3*order, - } - ) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) - vis = SiloVisualizer(discr, rcon) - - from grudge.timestep.runge_kutta import ( - LSRK4TimeStepper, ODE23TimeStepper, ODE45TimeStepper) - from grudge.timestep.dumka3 import Dumka3TimeStepper - #stepper = LSRK4TimeStepper(dtype=discr.default_scalar_type, - #vector_primitive_factory=discr.get_vector_primitive_factory()) - - stepper = ODE23TimeStepper(dtype=discr.default_scalar_type, - rtol=1e-6, - vector_primitive_factory=discr.get_vector_primitive_factory()) - # Dumka works kind of poorly - #stepper = Dumka3TimeStepper(dtype=discr.default_scalar_type, - #rtol=1e-7, pol_index=2, - #vector_primitive_factory=discr.get_vector_primitive_factory()) - - #from grudge.timestep.dumka3 import Dumka3TimeStepper - #stepper = Dumka3TimeStepper(3, rtol=1e-7) - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - logmgr = LogManager("cns-square-sp-%d.dat" % order, "w", rcon.communicator) - - add_run_info(logmgr) - add_general_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - from logpyle import LogQuantity - class ChangeSinceLastStep(LogQuantity): - """Records the change of a variable between a time step and the previous - one""" - - def __init__(self, name="change"): - LogQuantity.__init__(self, name, "1", "Change since last time step") - - self.old_fields = 0 - - def __call__(self): - result = discr.norm(fields - self.old_fields) - self.old_fields = fields - return result - - #logmgr.add_quantity(ChangeSinceLastStep()) - - add_simulation_quantities(logmgr) - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # filter setup ------------------------------------------------------------ - from grudge.discretization import Filter, ExponentialFilterResponseFunction - mode_filter = Filter(discr, - ExponentialFilterResponseFunction(min_amplification=0.95, order=6)) - - # timestep loop ------------------------------------------------------- - fields = square.volume_interpolant(0, discr) - - navierstokes_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = navierstokes_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=1000, - #max_steps=500, - logmgr=logmgr, - max_dt_getter=lambda t: next_dt, - taken_dt_getter=lambda: taken_dt) - - model_stepper = LSRK4TimeStepper() - next_dt = op.estimate_timestep(discr, - stepper=model_stepper, t=0, - max_eigenvalue=max_eigval[0]) - - for step, t, dt in step_it: - #if (step % 10000 == 0): #and step < 950000) or (step % 500 == 0 and step > 950000): - #if False: - if step % 5 == 0: - visf = vis.make_file("square-%d-%06d" % (order, step)) - - #from pyvisfile.silo import DB_VARTYPE_VECTOR - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - ], - expressions=[ - ("p", "(0.4)*(e- 0.5*(rho_u*u))"), - ], - time=t, step=step - ) - visf.close() - - if stepper.adaptive: - fields, t, taken_dt, next_dt = stepper(fields, t, dt, rhs) - else: - taken_dt = dt - fields = stepper(fields, t, dt, rhs) - dt = op.estimate_timestep(discr, - stepper=model_stepper, t=0, - max_eigenvalue=max_eigval[0]) - - #fields = mode_filter(fields) - - finally: - vis.close() - logmgr.save() - discr.close() - -if __name__ == "__main__": - main() diff --git a/unported-examples/gas_dynamics/wing.py b/unported-examples/gas_dynamics/wing.py deleted file mode 100644 index 2017d0f4d83a8c7e28cce3dca121968cf7965c3e..0000000000000000000000000000000000000000 --- a/unported-examples/gas_dynamics/wing.py +++ /dev/null @@ -1,242 +0,0 @@ -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la -from six.moves import zip - - - - -def make_wingmesh(): - import numpy - from math import pi, cos, sin - from meshpy.tet import MeshInfo, build - from meshpy.geometry import GeometryBuilder, Marker, \ - generate_extrusion, make_box - - geob = GeometryBuilder() - - profile_marker = Marker.FIRST_USER_MARKER - - wing_length = 2 - wing_subdiv = 5 - - rz_points = [ - (0, -wing_length*1.05), - (0.7, -wing_length*1.05), - ] + [ - (r, x) for x, r in zip( - numpy.linspace(-wing_length, 0, wing_subdiv, endpoint=False), - numpy.linspace(0.8, 1, wing_subdiv, endpoint=False)) - ] + [(1,0)] + [ - (r, x) for x, r in zip( - numpy.linspace(wing_length, 0, wing_subdiv, endpoint=False), - numpy.linspace(0.8, 1, wing_subdiv, endpoint=False)) - ][::-1] + [ - (0.7, wing_length*1.05), - (0, wing_length*1.05) - ] - - from meshpy.naca import get_naca_points - geob.add_geometry(*generate_extrusion( - rz_points=rz_points, - base_shape=get_naca_points("0012", number_of_points=20), - ring_markers=(wing_subdiv*2+4)*[profile_marker])) - - def deform_wing(p): - x, y, z = p - return numpy.array([ - x + 0.8*abs(z/wing_length)** 1.2, - y + 0.1*abs(z/wing_length)**2, - z]) - - geob.apply_transform(deform_wing) - - points, facets, facet_markers = make_box( - numpy.array([-1.5,-1,-wing_length-1], dtype=numpy.float64), - numpy.array([3,1,wing_length+1], dtype=numpy.float64)) - - geob.add_geometry(points, facets, facet_markers=facet_markers) - - mesh_info = MeshInfo() - geob.set(mesh_info) - mesh_info.set_holes([(0.5,0,0)]) - - mesh = build(mesh_info) - print("%d elements" % len(mesh.elements)) - - fvi2fm = mesh.face_vertex_indices_to_face_marker - - face_marker_to_tag = { - profile_marker: "noslip", - Marker.MINUS_X: "inflow", - Marker.PLUS_X: "outflow", - Marker.MINUS_Y: "inflow", - Marker.PLUS_Y: "inflow", - Marker.PLUS_Z: "inflow", - Marker.MINUS_Z: "inflow" - } - - def bdry_tagger(fvi, el, fn, all_v): - face_marker = fvi2fm[fvi] - return [face_marker_to_tag[face_marker]] - - from grudge.mesh import make_conformal_mesh - return make_conformal_mesh(mesh.points, mesh.elements, bdry_tagger) - - - - -def main(): - from grudge.backends import guess_run_context - rcon = guess_run_context( ["cuda", "mpi"]) - - if rcon.is_head_rank: - mesh = make_wingmesh() - #from grudge.mesh import make_rect_mesh - #mesh = make_rect_mesh( - # boundary_tagger=lambda fvi, el, fn, all_v: ["inflow"]) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - for order in [3]: - from pytools import add_python_path_relative_to_script - add_python_path_relative_to_script("..") - - from gas_dynamics_initials import UniformMachFlow - wing = UniformMachFlow(angle_of_attack=0) - - from grudge.models.gas_dynamics import GasDynamicsOperator - op = GasDynamicsOperator(dimensions=3, - gamma=wing.gamma, mu=wing.mu, - prandtl=wing.prandtl, spec_gas_const=wing.spec_gas_const, - bc_inflow=wing, bc_outflow=wing, bc_noslip=wing, - inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") - - discr = rcon.make_discretization(mesh_data, order=order, - debug=["cuda_no_plan", - #"cuda_dump_kernels", - #"dump_dataflow_graph", - #"dump_optemplate_stages", - #"dump_dataflow_graph", - #"print_op_code" - "cuda_no_metis", - ], - default_scalar_type=numpy.float64, - tune_for=op.sym_operator()) - - from grudge.visualization import SiloVisualizer, VtkVisualizer - #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) - vis = SiloVisualizer(discr, rcon) - - fields = wing.volume_interpolant(0, discr) - - navierstokes_ex = op.bind(discr) - - max_eigval = [0] - def rhs(t, q): - ode_rhs, speed = navierstokes_ex(t, q) - max_eigval[0] = speed - return ode_rhs - rhs(0, fields) - - if rcon.is_head_rank: - print("---------------------------------------------") - print("order %d" % order) - print("---------------------------------------------") - print("#elements=", len(mesh.elements)) - - from grudge.timestep import RK4TimeStepper - stepper = RK4TimeStepper() - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - logmgr = LogManager("navierstokes-%d.dat" % order, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ------------------------------------------------------- - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=200, - #max_steps=500, - logmgr=logmgr, - max_dt_getter=lambda t: 0.6 * op.estimate_timestep(discr, - stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) - - for step, t, dt in step_it: - if step % 200 == 0: - #if False: - visf = vis.make_file("wing-%d-%06d" % (order, step)) - - #rhs_fields = rhs(t, fields) - - from pyvisfile.silo import DB_VARTYPE_VECTOR - from grudge.discretization import ones_on_boundary - vis.add_data(visf, - [ - ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), - ("e", discr.convert_volume(op.e(fields), kind="numpy")), - ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), - ("u", discr.convert_volume(op.u(fields), kind="numpy")), - - #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), - #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), - #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), - ], - expressions=[ - ("p", "(0.4)*(e- 0.5*(rho_u*u))"), - ], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - t += dt - - finally: - vis.close() - logmgr.save() - discr.close() - - - - -if __name__ == "__main__": - main() diff --git a/unported-examples/heat/heat.py b/unported-examples/heat/heat.py deleted file mode 100644 index 0aa8fa92f308e171b1030c2210e6765058a47b6c..0000000000000000000000000000000000000000 --- a/unported-examples/heat/heat.py +++ /dev/null @@ -1,183 +0,0 @@ -from __future__ import absolute_import -from __future__ import print_function - -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - - -import numpy -import numpy.linalg as la - - - - -def main(write_output=True) : - from math import sin, cos, pi, exp, sqrt - from grudge.data import TimeConstantGivenFunction, \ - ConstantGivenFunction - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - dim = 2 - - def boundary_tagger(fvi, el, fn, all_v): - if el.face_normals[fn][0] > 0: - return ["dirichlet"] - else: - return ["neumann"] - - if dim == 2: - if rcon.is_head_rank: - from grudge.mesh.generator import make_disk_mesh - mesh = make_disk_mesh(r=0.5, boundary_tagger=boundary_tagger) - elif dim == 3: - if rcon.is_head_rank: - from grudge.mesh.generator import make_ball_mesh - mesh = make_ball_mesh(max_volume=0.001) - else: - raise RuntimeError("bad number of dimensions") - - if rcon.is_head_rank: - print("%d elements" % len(mesh.elements)) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - discr = rcon.make_discretization(mesh_data, order=3, - debug=["cuda_no_plan"], - default_scalar_type=numpy.float64) - - if write_output: - from grudge.visualization import VtkVisualizer - vis = VtkVisualizer(discr, rcon, "fld") - - def u0(x, el): - if la.norm(x) < 0.2: - return 1 - else: - return 0 - - def coeff(x, el): - if x[0] < 0: - return 0.25 - else: - return 1 - - def dirichlet_bc(t, x): - return 0 - - def neumann_bc(t, x): - return 2 - - from grudge.models.diffusion import DiffusionOperator - op = DiffusionOperator(discr.dimensions, - #coeff=coeff, - dirichlet_tag="dirichlet", - dirichlet_bc=TimeConstantGivenFunction(ConstantGivenFunction(0)), - neumann_tag="neumann", - neumann_bc=TimeConstantGivenFunction(ConstantGivenFunction(1)) - ) - u = discr.interpolate_volume_function(u0) - - # diagnostics setup ------------------------------------------------------- - from logpyle import LogManager, \ - add_general_quantities, \ - add_simulation_quantities, \ - add_run_info - - if write_output: - log_file_name = "heat.dat" - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - - from grudge.log import LpNorm - u_getter = lambda: u - logmgr.add_quantity(LpNorm(u_getter, discr, 1, name="l1_u")) - logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) - - logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) - - # timestep loop ----------------------------------------------------------- - from grudge.timestep.runge_kutta import LSRK4TimeStepper, ODE45TimeStepper - from grudge.timestep.dumka3 import Dumka3TimeStepper - #stepper = LSRK4TimeStepper() - stepper = Dumka3TimeStepper(3, rtol=1e-6, rcon=rcon, - vector_primitive_factory=discr.get_vector_primitive_factory(), - dtype=discr.default_scalar_type) - #stepper = ODE45TimeStepper(rtol=1e-6, rcon=rcon, - #vector_primitive_factory=discr.get_vector_primitive_factory(), - #dtype=discr.default_scalar_type) - stepper.add_instrumentation(logmgr) - - rhs = op.bind(discr) - try: - next_dt = op.estimate_timestep(discr, - stepper=LSRK4TimeStepper(), t=0, fields=u) - - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=0.1, logmgr=logmgr, - max_dt_getter=lambda t: next_dt, - taken_dt_getter=lambda: taken_dt) - - for step, t, dt in step_it: - if step % 10 == 0 and write_output: - visf = vis.make_file("fld-%04d" % step) - vis.add_data(visf, [ - ("u", discr.convert_volume(u, kind="numpy")), - ], time=t, step=step) - visf.close() - - u, t, taken_dt, next_dt = stepper(u, t, next_dt, rhs) - #u = stepper(u, t, dt, rhs) - - assert discr.norm(u) < 1 - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - - - - -if __name__ == "__main__": - main() - - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_heat(): - main(write_output=False) diff --git a/unported-examples/maxwell/.gitignore b/unported-examples/maxwell/.gitignore deleted file mode 100644 index 641163859ce66ad7bebf63db3035f0d58abef797..0000000000000000000000000000000000000000 --- a/unported-examples/maxwell/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bessel_zeros.py -2d_cavity diff --git a/unported-examples/maxwell/generate-bessel-zeros.py b/unported-examples/maxwell/generate-bessel-zeros.py deleted file mode 100644 index 172d491662a16a329850857af48ef5792eb2b18c..0000000000000000000000000000000000000000 --- a/unported-examples/maxwell/generate-bessel-zeros.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import absolute_import -from six.moves import range -def main(): - import scipy.special - - maxnu = 10 - n_zeros = 20 - - zeros = [] - for n in range(0,maxnu+1): - zeros.append(list(scipy.special.jn_zeros(n, n_zeros))) - - outf = open("bessel_zeros.py", "w").write("bessel_zeros = %s" % zeros) - -if __name__ == "__main__": - main() diff --git a/unported-examples/maxwell/inhomogeneous_waveguide.mac b/unported-examples/maxwell/inhomogeneous_waveguide.mac deleted file mode 100644 index 28a582f428be9e3193e4b012f4029d45464f0db9..0000000000000000000000000000000000000000 --- a/unported-examples/maxwell/inhomogeneous_waveguide.mac +++ /dev/null @@ -1,39 +0,0 @@ - -/* -From Robert E. Collin, Field Theory of Guided Waves, p. 413, chaper 6 - -Find the cut-off frequency of a rectangular waveguide partially filled with a dielectric slab, -in order to find the resonant frequency of an inhomogeneous 2D cavity. - -Take (5a), the transcendental equation for h and l, and substitute for their definitions in terms of gamma -Then solve for the condition that gamma is 0, for the mode with m=0. -t - width of dielectric section -d - width of air section -kappa - relative permittivity -k_0 - free space wavenumber -gamma - waveguide wavenumber -l - transverse wavenumber in dielectric -h - transverse wavenumber in air -*/ - -trans_eq : h*tan(l*t) + l*tan(h*d); -l_gamma : sqrt(gamma^2 - (m*pi/b)^2 + kappa*k_0^2); -h_gamma : sqrt(gamma^2 - (m*pi/b)^2 + k_0^2); -l_simp : l_gamma, gamma=0, m=0; -h_simp : h_gamma, gamma=0, m=0; - -subst(h_gamma, h, trans_eq)$ -subst(l_gamma, l, %)$ -subst(0, m, %)$ -trans_eq2 : subst(0, gamma, %); - -c : 2.99792458e8$ -plot2d([trans_eq2], [f,0.1e9,1.4e9], [y, -1000, 1000]), t = 50e-3, d=100e-3, kappa=2, k_0 = 2*%pi*f/c$ -f_sol : find_root(trans_eq2, f, 0.8e9, 1e9), t = 50e-3, d = 100e-3, kappa = 2, k_0 = 2*%pi*f/c; -h_simp: float(2*%pi*f_sol/c); -sqrt(kappa)*2*%pi*f_sol/c, kappa=2$ -l_simp: float(%); - -%pi*a/(a-d-sqrt(kappa)), a=150e-3, d=100e-3, kappa=2; -float(%); - diff --git a/unported-examples/maxwell/maxwell-2d.py b/unported-examples/maxwell/maxwell-2d.py deleted file mode 100644 index 802f391cdeb6516a8d0bd502a3f72c990134f049..0000000000000000000000000000000000000000 --- a/unported-examples/maxwell/maxwell-2d.py +++ /dev/null @@ -1,164 +0,0 @@ -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -"Maxwell's equation example with fixed material coefficients" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy.linalg as la - - -def main(write_output=True): - from math import sqrt, pi, exp - from os.path import join - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) - mu0 = 4*pi*1e-7 # N/A**2. - epsilon = 1*epsilon0 - mu = 1*mu0 - - output_dir = "maxwell-2d" - import os - if not os.access(output_dir, os.F_OK): - os.makedirs(output_dir) - - from grudge.mesh.generator import make_disk_mesh - mesh = make_disk_mesh(r=0.5, max_area=1e-3) - - if rcon.is_head_rank: - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - class CurrentSource: - shape = (3,) - - def __call__(self, x, el): - return [0,0,exp(-80*la.norm(x))] - - order = 3 - final_time = 1e-8 - discr = rcon.make_discretization(mesh_data, order=order, - debug=["cuda_no_plan"]) - - from grudge.visualization import VtkVisualizer - if write_output: - vis = VtkVisualizer(discr, rcon, join(output_dir, "em-%d" % order)) - - if rcon.is_head_rank: - print("order %d" % order) - print("#elements=", len(mesh.elements)) - - from grudge.mesh import BTAG_ALL, BTAG_NONE - from grudge.models.em import TMMaxwellOperator - from grudge.data import make_tdep_given, TimeIntervalGivenFunction - op = TMMaxwellOperator(epsilon, mu, flux_type=1, - current=TimeIntervalGivenFunction( - make_tdep_given(CurrentSource()), off_time=final_time/10), - absorb_tag=BTAG_ALL, pec_tag=BTAG_NONE) - fields = op.assemble_eh(discr=discr) - - from grudge.timestep import LSRK4TimeStepper - stepper = LSRK4TimeStepper() - from time import time - last_tstep = time() - t = 0 - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - if write_output: - log_file_name = join(output_dir, "maxwell-%d.dat" % order) - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - from logpyle import IntervalTimer - vis_timer = IntervalTimer("t_vis", "Time spent visualizing") - logmgr.add_quantity(vis_timer) - - from grudge.log import EMFieldGetter, add_em_quantities - field_getter = EMFieldGetter(discr, op, lambda: fields) - add_em_quantities(logmgr, op, field_getter) - - logmgr.add_watches(["step.max", "t_sim.max", - ("W_field", "W_el+W_mag"), "t_step.max"]) - - # timestep loop ------------------------------------------------------- - rhs = op.bind(discr) - - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=final_time, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, fields=fields)) - - for step, t, dt in step_it: - if step % 10 == 0 and write_output: - e, h = op.split_eh(fields) - visf = vis.make_file(join(output_dir, "em-%d-%04d" % (order, step))) - vis.add_data(visf, - [ - ("e", discr.convert_volume(e, "numpy")), - ("h", discr.convert_volume(h, "numpy")), - ], - time=t, step=step - ) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - assert discr.norm(fields) < 0.03 - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - -if __name__ == "__main__": - import cProfile as profile - #profile.run("main()", "wave2d.prof") - main() - - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_maxwell_2d(): - main(write_output=False) diff --git a/unported-examples/maxwell/maxwell-pml.py b/unported-examples/maxwell/maxwell-pml.py deleted file mode 100644 index 2af61e4ae288cde9f6eb159a1fdb57754fe41bb9..0000000000000000000000000000000000000000 --- a/unported-examples/maxwell/maxwell-pml.py +++ /dev/null @@ -1,244 +0,0 @@ - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function - -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - - - -import numpy as np - - - - -def make_mesh(a, b, pml_width=0.25, **kwargs): - from meshpy.geometry import GeometryBuilder, make_circle - geob = GeometryBuilder() - - circle_centers = [(-1.5, 0), (1.5, 0)] - for cent in circle_centers: - geob.add_geometry(*make_circle(1, cent)) - - geob.wrap_in_box(1) - geob.wrap_in_box(pml_width) - - mesh_mod = geob.mesher_module() - mi = mesh_mod.MeshInfo() - geob.set(mi) - - mi.set_holes(circle_centers) - - built_mi = mesh_mod.build(mi, **kwargs) - - def boundary_tagger(fvi, el, fn, points): - return [] - - from grudge.mesh import make_conformal_mesh_ext - from grudge.mesh.element import Triangle - pts = np.asarray(built_mi.points, dtype=np.float64) - return make_conformal_mesh_ext( - pts, - [Triangle(i, el, pts) - for i, el in enumerate(built_mi.elements)], - boundary_tagger) - - - - -def main(write_output=True): - from grudge.timestep.runge_kutta import LSRK4TimeStepper - from math import sqrt, pi, exp - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) - mu0 = 4*pi*1e-7 # N/A**2. - epsilon = 1*epsilon0 - mu = 1*mu0 - - c = 1/sqrt(mu*epsilon) - - pml_width = 0.5 - #mesh = make_mesh(a=np.array((-1,-1,-1)), b=np.array((1,1,1)), - #mesh = make_mesh(a=np.array((-3,-3)), b=np.array((3,3)), - mesh = make_mesh(a=np.array((-1,-1)), b=np.array((1,1)), - #mesh = make_mesh(a=np.array((-2,-2)), b=np.array((2,2)), - pml_width=pml_width, max_volume=0.01) - - if rcon.is_head_rank: - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - class Current: - def volume_interpolant(self, t, discr): - from grudge.tools import make_obj_array - - result = discr.volume_zeros(kind="numpy", dtype=np.float64) - - omega = 6*c - if omega*t > 2*pi: - return make_obj_array([result, result, result]) - - x = make_obj_array(discr.nodes.T) - r = np.sqrt(np.dot(x, x)) - - idx = r<0.3 - result[idx] = (1+np.cos(pi*r/0.3))[idx] \ - *np.sin(omega*t)**3 - - result = discr.convert_volume(result, kind=discr.compute_kind, - dtype=discr.default_scalar_type) - return make_obj_array([-result, result, result]) - - order = 3 - discr = rcon.make_discretization(mesh_data, order=order, - debug=["cuda_no_plan"]) - - from grudge.visualization import VtkVisualizer - if write_output: - vis = VtkVisualizer(discr, rcon, "em-%d" % order) - - from grudge.mesh import BTAG_ALL, BTAG_NONE - from grudge.data import GivenFunction, TimeHarmonicGivenFunction, TimeIntervalGivenFunction - from grudge.models.em import MaxwellOperator - from grudge.models.pml import \ - AbarbanelGottliebPMLMaxwellOperator, \ - AbarbanelGottliebPMLTMMaxwellOperator, \ - AbarbanelGottliebPMLTEMaxwellOperator - - op = AbarbanelGottliebPMLTEMaxwellOperator(epsilon, mu, flux_type=1, - current=Current(), - pec_tag=BTAG_ALL, - absorb_tag=BTAG_NONE, - add_decay=True - ) - - fields = op.assemble_ehpq(discr=discr) - - stepper = LSRK4TimeStepper() - - if rcon.is_head_rank: - print("order %d" % order) - print("#elements=", len(mesh.elements)) - - # diagnostics setup --------------------------------------------------- - from logpyle import LogManager, add_general_quantities, \ - add_simulation_quantities, add_run_info - - if write_output: - log_file_name = "maxwell-%d.dat" % order - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - stepper.add_instrumentation(logmgr) - - from logpyle import IntervalTimer - vis_timer = IntervalTimer("t_vis", "Time spent visualizing") - logmgr.add_quantity(vis_timer) - - from grudge.log import EMFieldGetter, add_em_quantities - field_getter = EMFieldGetter(discr, op, lambda: fields) - add_em_quantities(logmgr, op, field_getter) - - logmgr.add_watches(["step.max", "t_sim.max", ("W_field", "W_el+W_mag"), "t_step.max"]) - - from grudge.log import LpNorm - class FieldIdxGetter: - def __init__(self, whole_getter, idx): - self.whole_getter = whole_getter - self.idx = idx - - def __call__(self): - return self.whole_getter()[self.idx] - - # timestep loop ------------------------------------------------------- - - t = 0 - pml_coeff = op.coefficients_from_width(discr, width=pml_width) - rhs = op.bind(discr, pml_coeff) - - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=4/c, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, fields=fields)) - - for step, t, dt in step_it: - if step % 10 == 0 and write_output: - e, h, p, q = op.split_ehpq(fields) - visf = vis.make_file("em-%d-%04d" % (order, step)) - #pml_rhs_e, pml_rhs_h, pml_rhs_p, pml_rhs_q = \ - #op.split_ehpq(rhs(t, fields)) - j = Current().volume_interpolant(t, discr) - vis.add_data(visf, [ - ("e", discr.convert_volume(e, "numpy")), - ("h", discr.convert_volume(h, "numpy")), - ("p", discr.convert_volume(p, "numpy")), - ("q", discr.convert_volume(q, "numpy")), - ("j", discr.convert_volume(j, "numpy")), - #("pml_rhs_e", pml_rhs_e), - #("pml_rhs_h", pml_rhs_h), - #("pml_rhs_p", pml_rhs_p), - #("pml_rhs_q", pml_rhs_q), - #("max_rhs_e", max_rhs_e), - #("max_rhs_h", max_rhs_h), - #("max_rhs_p", max_rhs_p), - #("max_rhs_q", max_rhs_q), - ], - time=t, step=step) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - _, _, energies_data = logmgr.get_expr_dataset("W_el+W_mag") - energies = [value for tick_nbr, value in energies_data] - - assert energies[-1] < max(energies) * 1e-2 - - finally: - logmgr.close() - - if write_output: - vis.close() - -if __name__ == "__main__": - main() - - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_maxwell_pml(): - main(write_output=False) diff --git a/unported-examples/maxwell/notes.tm b/unported-examples/maxwell/notes.tm deleted file mode 100644 index bd159b24881bbcbd6561a2e048c8cd43dff79f8f..0000000000000000000000000000000000000000 --- a/unported-examples/maxwell/notes.tm +++ /dev/null @@ -1,403 +0,0 @@ -<TeXmacs|1.0.6> - -<style|<tuple|generic|maxima|axiom>> - -<\body> - <section|Cylindrical TM Maxwell Cavity Mode> - - <with|prog-language|axiom|prog-session|default|<\session> - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - )clear all - </input> - - <\output> - \ \ \ All user variables and function definitions have been cleared. - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - )library )dir "/home/andreas/axiom" - </input> - - <\output> - \ \ \ TexFormat is already explicitly exposed in frame initial\ - - \ \ \ TexFormat will be automatically loaded when needed from\ - - \ \ \ \ \ \ /home/andreas/axiom/TEX.NRLIB/code - - \ \ \ TexFormat1 is already explicitly exposed in frame initial\ - - \ \ \ TexFormat1 will be automatically loaded when needed from\ - - \ \ \ \ \ \ /home/andreas/axiom/TEX1.NRLIB/code - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - J:=operator 'J - </input> - - <\output> - <with|mode|math|math-display|true|J<leqno>(1)> - - <axiomtype|BasicOperator > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - psi(rho,phi) == J(gamma*rho)*exp(PP*%i*m*phi) - </input> - - <\output> - <axiomtype|Void > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - psiglob:=psi(sqrt(x^2+y^2),atan(y/x)) - </input> - - <\output> - \ \ \ Compiling function psi with type (Expression Integer,Expression\ - - \ \ \ \ \ \ Integer) -\<gtr\> Expression Complex Integer\ - - <with|mode|math|math-display|true|J<left|(>\<gamma\><sqrt|y<rsup|2>+x<rsup|2>><right|)>e<rsup|<left|(>i*P*P*m*arctan - <left|(><frac|y|x><right|)><right|)>><leqno>(3)> - - <axiomtype|Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - D(psi(rho,phi),rho) - </input> - - <\output> - \ \ \ Compiling function psi with type (Variable rho,Variable phi) - -\<gtr\>\ - - \ \ \ \ \ \ Expression Complex Integer\ - - <with|mode|math|math-display|true|\<gamma\>e<rsup|<left|(>i*P*P*m\<phi\><right|)>>J<rsub| - ><rsup|,><left|(>\<gamma\>\<rho\><right|)><leqno>(5)> - - <axiomtype|Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - cross(vector [0,0,1], vector [x,y,0]) - </input> - - <\output> - <with|mode|math|math-display|true|<left|[>-y,<space|0.5spc>x,<space|0.5spc>0<right|]><leqno>(7)> - - <axiomtype|Vector Polynomial Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - \; - </input> - </session>> - - <section|Rectangular Cavity Mode> - - According to Jackson, p. 357, (8.17), we need to solve the Helmholtz - equation - - <\eqnarray*> - <tformat|<cwith|1|1|3|3|cell-halign|l>|<table|<row|<cell|(\<nabla\><rsup|2>+\<mu\>\<varepsilon\>\<omega\><rsup|2>)<matrix|<tformat|<table|<row|<cell|\<b-E\>>>|<row|<cell|\<b-B\>>>>>>>|<cell|=>|<cell|\<b-0\>,>>>> - </eqnarray*> - - subject to <with|mode|math|n\<times\>\<b-E\>=0> and - <with|mode|math|n\<cdot\>\<b-B\>=0>. The ansatz is - - <\equation*> - \<b-E\>=<matrix|<tformat|<table|<row|<cell|E<rsub|x,x>(x)E<rsub|x,y>(y)E<rsub|x,z>(z)>>|<row|<cell|E<rsub|y,x>(x)E<rsub|y,y>(y)E<rsub|y,z>(z)>>|<row|<cell|E<rsub|z,x>(x)E<rsub|z,y>(y)E<rsub|z,z>(z)>>>>> - </equation*> - - and likewise for <with|mode|math|\<b-B\>>. The boundary conditions are - - <\eqnarray*> - <tformat|<table|<row|<cell|E<rsub|x>(x,<with|math-level|1|<tabular|<tformat|<table|<row|<cell|0>>|<row|<cell|b>>>>>>,z)>|<cell|=>|<cell|0,>>|<row|<cell|E<rsub|x>(x,y,<with|math-level|1|<tabular|<tformat|<table|<row|<cell|0>>|<row|<cell|c>>>>>>)>|<cell|=>|<cell|0,>>>> - </eqnarray*> - - and so on, as well as - - <\eqnarray*> - <tformat|<table|<row|<cell|H<rsub|x>(<with|math-level|1|<tabular|<tformat|<table|<row|<cell|0>>|<row|<cell|a>>>>>>,y,z)>|<cell|=>|<cell|0.>>>> - </eqnarray*> - - So - - <\equation*> - E<rsub|x>=\<alpha\><rsub|x>exp(i\<beta\><rsub|x>x)sin<left|(><frac|n\<pi\>y|b><right|)>sin<left|(><frac|o\<pi\>z|c><right|)>exp(-i\<omega\>t)=\<alpha\><rsub|x>e<rsub|x>s<rsub|y>s<rsub|z> - </equation*> - - and analogous terms for <with|mode|math|E<rsub|y>> and - <with|mode|math|E<rsub|z>> satisfy the first batch of boundary conditions. - Because of the Helmholtz equation, we find that - <with|mode|math|\<beta\><rsub|x>=m\<pi\>/a>; otherwise, not all vector - components would share the same eigenvalue, which would not solve the - equation. - - <with|prog-language|axiom|prog-session|default|<\session> - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - )clear all - </input> - - <\output> - \ \ \ All user variables and function definitions have been cleared. - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - )library )dir "/home/andreas/axiom" - </input> - - <\output> - \ \ \ TexFormat is already explicitly exposed in frame initial\ - - \ \ \ TexFormat will be automatically loaded when needed from\ - - \ \ \ \ \ \ /home/andreas/axiom/TEX.NRLIB/code - - \ \ \ TexFormat1 is already explicitly exposed in frame initial\ - - \ \ \ TexFormat1 will be automatically loaded when needed from\ - - \ \ \ \ \ \ /home/andreas/axiom/TEX1.NRLIB/code - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - factors:=[f,g,h]; - </input> - - <\output> - <axiomtype|List OrderedVariableList [f,g,h] > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - coord := [x,y,z]; - </input> - - <\output> - <axiomtype|List OrderedVariableList [x,y,z] > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - curl(v)== vector [D(v.3,y)-D(v.2,z),D(v.1,z)-D(v.3,x),D(v.2,x)-D(v.1,y)]; - </input> - - <\output> - <axiomtype|Void > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - c:=1/sqrt(epsilon*mu); - </input> - - <\output> - <axiomtype|Expression Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - sines(i) == sin(factors.i*coord.i); - </input> - - <\output> - <axiomtype|Void > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - cosines(i) == cos(factors.i*coord.i); - </input> - - <\output> - <axiomtype|Void > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - k:=sqrt(f^2+g^2+h^2);omega:=k*c; - </input> - - <\output> - <axiomtype|Expression Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - zdep1:=exp(%i*h*z); zdep2:=exp(-%i*h*z); - </input> - - <\output> - <axiomtype|Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - zf1:=1; zf2:=-1; - </input> - - <\output> - <axiomtype|Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - zdep:=zf1*zdep1 + zf2*zdep2; - </input> - - <\output> - <axiomtype|Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - C:=%i/(f^2+g^2); - </input> - - <\output> - <axiomtype|Fraction Polynomial Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - efield := vector [ - - C*f*h*cosines(1)* \ sines(2)*(zf1*zdep1-zf2*zdep2), - - C*g*h* \ sines(1)*cosines(2)*(zf1*zdep1-zf2*zdep2), - - \ \ \ \ \ \ \ \ sines(1)* \ sines(2)*zdep]; - </input> - - <\output> - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - hfield:=1/(-%i*omega*mu)*(-curl efield); - </input> - - <\output> - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - efield2:=1/(-%i*omega*epsilon)*(curl hfield); - </input> - - <\output> - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - efield2-efield - </input> - - <\output> - <with|mode|math|math-display|true|<left|[>0,<space|0.5spc>0,<space|0.5spc>0<right|]><leqno>(71)> - - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - hfield - </input> - - <\output> - <with|mode|math|math-display|true|<left|[><frac|<left|(><left|(>-i*g*h<rsup|2>-i*g<rsup|3>-i*f<rsup|2>g<right|)>cos - <left|(>g*y<right|)>e<rsup|<left|(>i*h*z<right|)>>+<left|(>i*g*h<rsup|2>+i*g<rsup|3>+i*f<rsup|2>g<right|)>cos - <left|(>g*y<right|)>e<rsup|<left|(>-i*h*z<right|)>><right|)>sin - <left|(>f*x<right|)><sqrt|\<epsilon\>\<mu\>>|<left|(>g<rsup|2>+f<rsup|2><right|)>\<mu\><sqrt|h<rsup|2>+g<rsup|2>+f<rsup|2>>>,<space|0.5spc><frac|<left|(><left|(>i*f*h<rsup|2>+i*f*g<rsup|2>+i*f<rsup|3><right|)>cos - <left|(>f*x<right|)>e<rsup|<left|(>i*h*z<right|)>>+<left|(>-i*f*h<rsup|2>-i*f*g<rsup|2>-i*f<rsup|3><right|)>cos - <left|(>f*x<right|)>e<rsup|<left|(>-i*h*z<right|)>><right|)>sin - <left|(>g*y<right|)><sqrt|\<epsilon\>\<mu\>>|<left|(>g<rsup|2>+f<rsup|2><right|)>\<mu\><sqrt|h<rsup|2>+g<rsup|2>+f<rsup|2>>>,<space|0.5spc>0<right|]><leqno>(72)> - - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - hfield2:=vector [ - - -%i*g/(f^2+g^2)*epsilon*omega*sines(1)*cosines(2)*(zf1*zdep1+zf2*zdep2), - - \ %i*f/(f^2+g^2)*epsilon*omega*cosines(1)*sines(2)*(zf1*zdep1+zf2*zdep2), - - 0] - </input> - - <\output> - <with|mode|math|math-display|true|<left|[><frac|<left|(>-i\<epsilon\>g*cos - <left|(>g*y<right|)>e<rsup|<left|(>i*h*z<right|)>>+i\<epsilon\>g*cos - <left|(>g*y<right|)>e<rsup|<left|(>-i*h*z<right|)>><right|)>sin - <left|(>f*x<right|)><sqrt|h<rsup|2>+g<rsup|2>+f<rsup|2>>|<left|(>g<rsup|2>+f<rsup|2><right|)><sqrt|\<epsilon\>\<mu\>>>,<space|0.5spc><frac|<left|(>i\<epsilon\>f*cos - <left|(>f*x<right|)>e<rsup|<left|(>i*h*z<right|)>>-i\<epsilon\>f*cos - <left|(>f*x<right|)>e<rsup|<left|(>-i*h*z<right|)>><right|)>sin - <left|(>g*y<right|)><sqrt|h<rsup|2>+g<rsup|2>+f<rsup|2>>|<left|(>g<rsup|2>+f<rsup|2><right|)><sqrt|\<epsilon\>\<mu\>>>,<space|0.5spc>0<right|]><leqno>(73)> - - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - hfield-hfield2 - </input> - - <\output> - <with|mode|math|math-display|true|<left|[>0,<space|0.5spc>0,<space|0.5spc>0<right|]><leqno>(74)> - - <axiomtype|Vector Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - bcs:=[ - - eval(efield.1, z=0), - - eval(efield.2, z=0), - - eval(efield.3, z=0), - - eval(hfield.1, x=0), - - eval(hfield.2, y=0), - - eval(hfield.3, z=0) - - ] - </input> - - <\output> - <with|mode|math|math-display|true|<left|[>0,<space|0.5spc><frac|2i*g*h*cos - <left|(>g*y<right|)>sin <left|(>f*x<right|)>|g<rsup|2>+f<rsup|2>>,<space|0.5spc><frac|2i*f*h*cos - <left|(>f*x<right|)>sin <left|(>g*y<right|)>|g<rsup|2>+f<rsup|2>>,<space|0.5spc>0,<space|0.5spc>0,<space|0.5spc>0<right|]><leqno>(76)> - - <axiomtype|List Expression Complex Integer > - </output> - - <\input|<with|color|red|<with|mode|math|\<rightarrow\>> >> - \; - </input> - </session>> - - \; -</body> - -<\initial> - <\collection> - <associate|page-type|letter> - </collection> -</initial> - -<\references> - <\collection> - <associate|auto-1|<tuple|1|1>> - <associate|auto-2|<tuple|2|1>> - <associate|auto-3|<tuple|3|?>> - </collection> -</references> - -<\auxiliary> - <\collection> - <\associate|toc> - <vspace*|1fn><with|font-series|<quote|bold>|math-font-series|<quote|bold>|Cylindrical - TM Maxwell Cavity Mode> <datoms|<macro|x|<repeat|<arg|x>|<with|font-series|medium|<with|font-size|1|<space|0.2fn>.<space|0.2fn>>>>>|<htab|5mm>> - <no-break><pageref|auto-1><vspace|0.5fn> - - <vspace*|1fn><with|font-series|<quote|bold>|math-font-series|<quote|bold>|Rectangular - Cavity Mode> <datoms|<macro|x|<repeat|<arg|x>|<with|font-series|medium|<with|font-size|1|<space|0.2fn>.<space|0.2fn>>>>>|<htab|5mm>> - <no-break><pageref|auto-2><vspace|0.5fn> - </associate> - </collection> -</auxiliary> \ No newline at end of file diff --git a/unported-examples/poisson/helmholtz.py b/unported-examples/poisson/helmholtz.py deleted file mode 100644 index 1814e192265f3a95e623fdf5810ba750869fbe95..0000000000000000000000000000000000000000 --- a/unported-examples/poisson/helmholtz.py +++ /dev/null @@ -1,185 +0,0 @@ -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la -from grudge.tools import Reflection, Rotation - - - -class ResidualPrinter: - def __init__(self, compute_resid=None): - self.count = 0 - self.compute_resid = compute_resid - - def __call__(self, cur_sol): - import sys - - if cur_sol is not None: - if self.count % 20 == 0: - sys.stdout.write("IT %8d %g \r" % ( - self.count, la.norm(self.compute_resid(cur_sol)))) - else: - sys.stdout.write("IT %8d \r" % self.count) - self.count += 1 - sys.stdout.flush() - - - - -def main(write_output=True): - from grudge.data import GivenFunction, ConstantGivenFunction - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - dim = 2 - - def boundary_tagger(fvi, el, fn, points): - from math import atan2, pi - normal = el.face_normals[fn] - if -90/180*pi < atan2(normal[1], normal[0]) < 90/180*pi: - return ["neumann"] - else: - return ["dirichlet"] - - def dirichlet_boundary_tagger(fvi, el, fn, points): - return ["dirichlet"] - - if dim == 2: - if rcon.is_head_rank: - from grudge.mesh.generator import make_disk_mesh - mesh = make_disk_mesh(r=0.5, - boundary_tagger=dirichlet_boundary_tagger, - max_area=1e-3) - elif dim == 3: - if rcon.is_head_rank: - from grudge.mesh.generator import make_ball_mesh - mesh = make_ball_mesh(max_volume=0.0001, - boundary_tagger=lambda fvi, el, fn, points: - ["dirichlet"]) - else: - raise RuntimeError("bad number of dimensions") - - if rcon.is_head_rank: - print("%d elements" % len(mesh.elements)) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - discr = rcon.make_discretization(mesh_data, order=5, - debug=[]) - - def dirichlet_bc(x, el): - from math import sin - return sin(10*x[0]) - - def rhs_c(x, el): - if la.norm(x) < 0.1: - return 1000 - else: - return 0 - - def my_diff_tensor(): - result = numpy.eye(dim) - result[0,0] = 0.1 - return result - - try: - from grudge.models.poisson import ( - PoissonOperator, - HelmholtzOperator) - from grudge.second_order import \ - IPDGSecondDerivative, LDGSecondDerivative, \ - StabilizedCentralSecondDerivative - - k = 1 - - from grudge.mesh import BTAG_NONE, BTAG_ALL - op = HelmholtzOperator(k, discr.dimensions, - #diffusion_tensor=my_diff_tensor(), - - #dirichlet_tag="dirichlet", - #neumann_tag="neumann", - - dirichlet_tag=BTAG_ALL, - neumann_tag=BTAG_NONE, - - #dirichlet_tag=BTAG_ALL, - #neumann_tag=BTAG_NONE, - - #dirichlet_bc=GivenFunction(dirichlet_bc), - dirichlet_bc=ConstantGivenFunction(0), - neumann_bc=ConstantGivenFunction(-10), - - scheme=StabilizedCentralSecondDerivative(), - #scheme=LDGSecondDerivative(), - #scheme=IPDGSecondDerivative(), - ) - bound_op = op.bind(discr) - - if False: - from grudge.iterative import parallel_cg - u = -parallel_cg(rcon, -bound_op, - bound_op.prepare_rhs(discr.interpolate_volume_function(rhs_c)), - debug=20, tol=5e-4, - dot=discr.nodewise_dot_product, - x=discr.volume_zeros()) - else: - rhs = bound_op.prepare_rhs(discr.interpolate_volume_function(rhs_c)) - def compute_resid(x): - return bound_op(x)-rhs - - from scipy.sparse.linalg import minres, LinearOperator - u, info = minres( - LinearOperator( - (len(discr), len(discr)), - matvec=bound_op, dtype=bound_op.dtype), - rhs, - callback=ResidualPrinter(compute_resid), - tol=1e-5) - print() - if info != 0: - raise RuntimeError("gmres reported error %d" % info) - print("finished gmres") - - print(la.norm(bound_op(u)-rhs)/la.norm(rhs)) - - if write_output: - from grudge.visualization import SiloVisualizer, VtkVisualizer - vis = VtkVisualizer(discr, rcon) - visf = vis.make_file("fld") - vis.add_data(visf, [ ("sol", discr.convert_volume(u, kind="numpy")), ]) - visf.close() - finally: - discr.close() - - - - - -if __name__ == "__main__": - main() diff --git a/unported-examples/poisson/poisson.py b/unported-examples/poisson/poisson.py deleted file mode 100644 index 58bb530ccb401d2ac1c09756750a9f7f096c1e26..0000000000000000000000000000000000000000 --- a/unported-examples/poisson/poisson.py +++ /dev/null @@ -1,145 +0,0 @@ -__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -import numpy -import numpy.linalg as la -from grudge.tools import Reflection, Rotation - - - - -def main(write_output=True): - from grudge.data import GivenFunction, ConstantGivenFunction - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - dim = 2 - - def boundary_tagger(fvi, el, fn, points): - from math import atan2, pi - normal = el.face_normals[fn] - if -90/180*pi < atan2(normal[1], normal[0]) < 90/180*pi: - return ["neumann"] - else: - return ["dirichlet"] - - if dim == 2: - if rcon.is_head_rank: - from grudge.mesh.generator import make_disk_mesh - mesh = make_disk_mesh(r=0.5, boundary_tagger=boundary_tagger, - max_area=1e-2) - elif dim == 3: - if rcon.is_head_rank: - from grudge.mesh.generator import make_ball_mesh - mesh = make_ball_mesh(max_volume=0.0001, - boundary_tagger=lambda fvi, el, fn, points: - ["dirichlet"]) - else: - raise RuntimeError("bad number of dimensions") - - if rcon.is_head_rank: - print("%d elements" % len(mesh.elements)) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - discr = rcon.make_discretization(mesh_data, order=5, - debug=[]) - - def dirichlet_bc(x, el): - from math import sin - return sin(10*x[0]) - - def rhs_c(x, el): - if la.norm(x) < 0.1: - return 1000 - else: - return 0 - - def my_diff_tensor(): - result = numpy.eye(dim) - result[0,0] = 0.1 - return result - - try: - from grudge.models.poisson import PoissonOperator - from grudge.second_order import \ - IPDGSecondDerivative, LDGSecondDerivative, \ - StabilizedCentralSecondDerivative - from grudge.mesh import BTAG_NONE, BTAG_ALL - op = PoissonOperator(discr.dimensions, - diffusion_tensor=my_diff_tensor(), - - #dirichlet_tag="dirichlet", - #neumann_tag="neumann", - - dirichlet_tag=BTAG_ALL, - neumann_tag=BTAG_NONE, - - #dirichlet_tag=BTAG_ALL, - #neumann_tag=BTAG_NONE, - - dirichlet_bc=GivenFunction(dirichlet_bc), - neumann_bc=ConstantGivenFunction(-10), - - scheme=StabilizedCentralSecondDerivative(), - #scheme=LDGSecondDerivative(), - #scheme=IPDGSecondDerivative(), - ) - bound_op = op.bind(discr) - - from grudge.iterative import parallel_cg - u = -parallel_cg(rcon, -bound_op, - bound_op.prepare_rhs(discr.interpolate_volume_function(rhs_c)), - debug=20, tol=5e-4, - dot=discr.nodewise_dot_product, - x=discr.volume_zeros()) - - if write_output: - from grudge.visualization import SiloVisualizer, VtkVisualizer - vis = VtkVisualizer(discr, rcon) - visf = vis.make_file("fld") - vis.add_data(visf, [ ("sol", discr.convert_volume(u, kind="numpy")), ]) - visf.close() - finally: - discr.close() - - - - - -if __name__ == "__main__": - main() - - - - -# entry points for py.test ---------------------------------------------------- -from pytools.test import mark_test -@mark_test.long -def test_poisson(): - main(write_output=False) diff --git a/unported-examples/wave/wiggly.py b/unported-examples/wave/wiggly.py deleted file mode 100644 index db383255fd8a1cb47958d05d33218298202d61eb..0000000000000000000000000000000000000000 --- a/unported-examples/wave/wiggly.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Wiggly geometry wave propagation.""" - -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -from six.moves import range - -__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -import numpy as np -from grudge.mesh import BTAG_ALL, BTAG_NONE # noqa - - -def main(write_output=True, - flux_type_arg="upwind", dtype=np.float64, debug=[]): - from math import sin, cos, pi, exp, sqrt # noqa - - from grudge.backends import guess_run_context - rcon = guess_run_context() - - if rcon.is_head_rank: - from grudge.mesh.reader.gmsh import generate_gmsh - mesh = generate_gmsh(GEOMETRY, 2, - allow_internal_boundaries=True, - force_dimension=2) - - print("%d elements" % len(mesh.elements)) - mesh_data = rcon.distribute_mesh(mesh) - else: - mesh_data = rcon.receive_mesh() - - discr = rcon.make_discretization(mesh_data, order=4, debug=debug, - default_scalar_type=dtype) - from grudge.timestep.runge_kutta import LSRK4TimeStepper - stepper = LSRK4TimeStepper(dtype=dtype) - - from grudge.visualization import VtkVisualizer - if write_output: - vis = VtkVisualizer(discr, rcon, "fld") - - source_center = 0 - source_width = 0.05 - source_omega = 3 - - import grudge.symbolic as sym - sym_x = sym.nodes(2) - sym_source_center_dist = sym_x - source_center - - from grudge.models.wave import StrongWaveOperator - op = StrongWaveOperator(-1, discr.dimensions, - source_f= - sym.FunctionSymbol("sin")(source_omega*sym.ScalarParameter("t")) - * sym.FunctionSymbol("exp")( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2), - dirichlet_tag="boundary", - neumann_tag=BTAG_NONE, - radiation_tag=BTAG_NONE, - flux_type=flux_type_arg - ) - - from grudge.tools import join_fields - fields = join_fields(discr.volume_zeros(dtype=dtype), - [discr.volume_zeros(dtype=dtype) for i in range(discr.dimensions)]) - - # diagnostics setup ------------------------------------------------------- - from logpyle import LogManager, \ - add_general_quantities, \ - add_simulation_quantities, \ - add_run_info - - if write_output: - log_file_name = "wiggly.dat" - else: - log_file_name = None - - logmgr = LogManager(log_file_name, "w", rcon.communicator) - add_run_info(logmgr) - add_general_quantities(logmgr) - add_simulation_quantities(logmgr) - discr.add_instrumentation(logmgr) - - stepper.add_instrumentation(logmgr) - - logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) - - # timestep loop ----------------------------------------------------------- - rhs = op.bind(discr) - try: - from grudge.timestep import times_and_steps - step_it = times_and_steps( - final_time=4, logmgr=logmgr, - max_dt_getter=lambda t: op.estimate_timestep(discr, - stepper=stepper, t=t, fields=fields)) - - for step, t, dt in step_it: - if step % 10 == 0 and write_output: - visf = vis.make_file("fld-%04d" % step) - - vis.add_data(visf, - [ - ("u", fields[0]), - ("v", fields[1:]), - ], - time=t, - step=step) - visf.close() - - fields = stepper(fields, t, dt, rhs) - - assert discr.norm(fields) < 1 - assert fields[0].dtype == dtype - - finally: - if write_output: - vis.close() - - logmgr.close() - discr.close() - -GEOMETRY = """ -w = 1; -dx = 0.2; -ch_width = 0.2; -rows = 4; - -Point(0) = {0,0,0}; -Point(1) = {w,0,0}; - -bottom_line = newl; -Line(bottom_line) = {0,1}; - -left_pts[] = { 0 }; -right_pts[] = { 1 }; - -left_pts[] = { }; -emb_lines[] = {}; - -For row In {1:rows} - If (row % 2 == 0) - // left - rp = newp; Point(rp) = {w,dx*row, 0}; - right_pts[] += {rp}; - - mp = newp; Point(mp) = {ch_width,dx*row, 0}; - emb_line = newl; Line(emb_line) = {mp,rp}; - emb_lines[] += {emb_line}; - EndIf - If (row % 2) - // right - lp = newp; Point(lp) = {0,dx*row, 0}; - left_pts[] += {lp}; - - mp = newp; Point(mp) = { w-ch_width,dx*row, 0}; - emb_line = newl; Line(emb_line) = {mp,lp}; - emb_lines[] += {emb_line}; - EndIf -EndFor - -lep = newp; Point(lep) = {0,(rows+1)*dx,0}; -rep = newp; Point(rep) = {w,(rows+1)*dx,0}; -top_line = newl; Line(top_line) = {lep,rep}; - -left_pts[] += { lep }; -right_pts[] += { rep }; - -lines[] = {bottom_line}; - -For i In {0:#right_pts[]-2} - l = newl; Line(l) = {right_pts[i], right_pts[i+1]}; - lines[] += {l}; -EndFor - -lines[] += {-top_line}; - -For i In {#left_pts[]-1:0:-1} - l = newl; Line(l) = {left_pts[i], left_pts[i-1]}; - lines[] += {l}; -EndFor - -Line Loop (1) = lines[]; - -Plane Surface (1) = {1}; -Physical Surface(1) = {1}; - -For i In {0:#emb_lines[]-1} - Line { emb_lines[i] } In Surface { 1 }; -EndFor - -boundary_lines[] = {}; -boundary_lines[] += lines[]; -boundary_lines[] += emb_lines[]; -Physical Line ("boundary") = boundary_lines[]; - -Mesh.CharacteristicLengthFactor = 0.4; -""" - -if __name__ == "__main__": - main() - - - - -