diff --git a/examples/advection/surface.py b/examples/advection/surface.py new file mode 100644 index 0000000000000000000000000000000000000000..69ab3a90c336bbaf360cecfea74af4c172a6e391 --- /dev/null +++ b/examples/advection/surface.py @@ -0,0 +1,211 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2020 Alexandru Fikl" + +__license__ = """ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" + +import os +import numpy as np +import numpy.linalg as la + +import pyopencl as cl +import pyopencl.array +import pyopencl.clmath + +from grudge import bind, sym +from pytools.obj_array import make_obj_array + +import logging +logger = logging.getLogger(__name__) + + +# {{{ plotting (keep in sync with `var-velocity.py`) + +class Plotter: + def __init__(self, queue, discr, order, visualize=True): + self.queue = queue + self.ambient_dim = discr.ambient_dim + self.dim = discr.dim + + self.visualize = visualize + if not self.visualize: + return + + if self.ambient_dim == 2: + import matplotlib.pyplot as pt + self.fig = pt.figure(figsize=(8, 8), dpi=300) + + volume_discr = discr.discr_from_dd(sym.DD_VOLUME) + x = volume_discr.nodes().with_queue(queue) + self.x = (2.0 * np.pi * (x[1] < 0) + cl.clmath.atan2(x[1], x[0])).get(queue) + elif self.ambient_dim == 3: + from grudge.shortcuts import make_visualizer + self.vis = make_visualizer(discr, vis_order=order) + else: + raise ValueError("unsupported dimension") + + def __call__(self, evt, basename, overwrite=True): + if not self.visualize: + return + + if self.ambient_dim == 2: + u = evt.state_component.get(self.queue) + + filename = "%s.png" % basename + if not overwrite and os.path.exists(filename): + from meshmode import FileExistsError + raise FileExistsError("output file '%s' already exists" % filename) + + ax = self.fig.gca() + ax.grid() + ax.plot(self.x, u, '-') + ax.plot(self.x, u, 'k.') + ax.set_xlabel(r"$\theta$") + ax.set_ylabel("$u$") + ax.set_title("t = {:.2f}".format(evt.t)) + + self.fig.savefig(filename) + self.fig.clf() + elif self.ambient_dim == 3: + self.vis.write_vtk_file("%s.vtu" % basename, [ + ("u", evt.state_component) + ], overwrite=overwrite) + else: + raise ValueError("unsupported dimension") + +# }}} + + +def main(ctx_factory, dim=2, order=4, product_tag=None, visualize=True): + cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + + # {{{ parameters + + # sphere radius + radius = 1.0 + # sphere resolution + resolution = 64 if dim == 2 else 1 + + # cfl + dt_factor = 1.0 + # final time + final_time = 1.0 + + # velocity field + sym_x = sym.nodes(dim) + c = make_obj_array([ + -sym_x[1], sym_x[0], 0.0 + ])[:dim] + norm_c = sym.sqrt((c**2).sum()) + # flux + flux_type = "central" + + # }}} + + # {{{ discretization + + if dim == 2: + from meshmode.mesh.generation import make_curve_mesh, ellipse + mesh = make_curve_mesh( + lambda t: radius * ellipse(1.0, t), + np.linspace(0.0, 1.0, resolution + 1), + order) + elif dim == 3: + from meshmode.mesh.generation import generate_icosphere + return generate_icosphere(radius, order=order, + uniform_refinement_rounds=resolution) + else: + raise ValueError("unsupported dimension") + + quad_tag_to_group_factory = {} + if product_tag == "none": + product_tag = None + + if product_tag: + from meshmode.discretization.poly_element import \ + QuadratureSimplexGroupFactory + quad_tag_to_group_factory = { + product_tag: QuadratureSimplexGroupFactory(order=4*order) + } + + from grudge import DGDiscretizationWithBoundaries + discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order, + quad_tag_to_group_factory=quad_tag_to_group_factory) + + # }}} + + # {{{ symbolic operators + + def f_gaussian(x): + return x[0] + + from grudge.models.advection import SurfaceAdvectionOperator + op = SurfaceAdvectionOperator(c, + flux_type=flux_type, + quad_tag=product_tag) + + bound_op = bind(discr, op.sym_operator()) + u = bind(discr, f_gaussian(sym_x))(queue, t=0) + + def rhs(t, u): + return bound_op(queue, t=t, u=u) + + # }}} + + # {{{ time stepping + + # compute time step + h_min = bind(discr, + sym.h_max_from_volume(discr.ambient_dim, dim=discr.dim))(queue) + dt = dt_factor * h_min/order**2 + nsteps = int(final_time // dt) + 1 + dt = final_time/nsteps + 1.0e-15 + + from grudge.shortcuts import set_up_rk4 + dt_stepper = set_up_rk4("u", dt, u, rhs) + plot = Plotter(queue, discr, order, visualize=visualize) + + 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): + if not isinstance(event, dt_stepper.StateComputed): + continue + + if step % 1 == 0: + norm_u = norm(queue, u=event.state_component) + plot(event, "fld-surface-%04d" % step) + + step += 1 + logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) + + # }}} + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("--dim", default=2, type=int) + parser.add_argument("--qtag", default="none") + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO) + main(cl.create_some_context, + dim=args.dim, + product_tag=args.qtag) diff --git a/grudge/models/advection.py b/grudge/models/advection.py index 36ce5c155bde62a7314a3ec6f0d828e8968946a6..ae3d8a33fda2fcd92320f1c239fc4c4b0473c7d7 100644 --- a/grudge/models/advection.py +++ b/grudge/models/advection.py @@ -182,4 +182,65 @@ class VariableCoefficientAdvectionOperator(AdvectionOperatorBase): )) # }}} + +# {{{ closed surface advection + +def v_dot_n_tpair(velocity, dd=None): + ambient_dim = len(velocity) + normal = sym.normal(dd, ambient_dim, dim=ambient_dim - 2) + + v_dot_n_i = sym.cse(velocity.dot(normal), "v_dot_normal") + v_dot_n_e = sym.cse(sym.OppositeInteriorFaceSwap()(v_dot_n_i)) + + return sym.TracePair(dd, v_dot_n_i, -v_dot_n_e) + + +def surface_advection_weak_flux(flux_type, u, velocity): + v_dot_n = v_dot_n_tpair(velocity, dd=u.dd) + + flux_type = flux_type.lower() + if flux_type == "central": + return u.avg * v_dot_n.avg + elif flux_type == "lf": + return u.avg * v_dot_n.avg + 0.5 * sym.fabs(v_dot_n) * (u.int - u.ext) + else: + raise ValueError("flux `{}` is not implemented".format(flux_type)) + + +class SurfaceAdvectionOperator(AdvectionOperatorBase): + def __init__(self, v, flux_type="central", quad_tag=None): + super(SurfaceAdvectionOperator, self).__init__( + v, inflow_u=None, flux_type=flux_type) + self.quad_tag = quad_tag + + def flux(self, u): + surf_v = sym.interp(sym.DD_VOLUME, u.dd)(self.v) + return surface_advection_weak_flux(self.flux_type, u, surf_v) + + def sym_operator(self): + u = sym.var("u") + + def flux(pair): + return sym.interp(pair.dd, face_dd)(self.flux(pair)) + + face_dd = sym.DOFDesc(sym.FACE_RESTR_ALL, self.quad_tag) + + quad_dd = sym.DOFDesc(sym.DTAG_VOLUME_ALL, self.quad_tag) + to_quad = sym.interp(sym.DD_VOLUME, quad_dd) + stiff_t_op = sym.stiffness_t(self.ambient_dim, + dd_in=quad_dd, dd_out=sym.DD_VOLUME) + + 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, sym.DD_VOLUME)( + flux(sym.int_tpair(u, self.quad_tag)) + ) + ) + +# }}} + # vim: foldmethod=marker