diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a1363379944a5745ceb49c0e493d80eb9335c79a --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +*.pdf diff --git a/examples/multiple-meshes.py b/examples/multiple-meshes.py index 84918e1779a34932ed6269a8884e30e63bb846d0..2dab3fabb65cc4b86477c5a7325acaff752e3ad2 100644 --- a/examples/multiple-meshes.py +++ b/examples/multiple-meshes.py @@ -1,6 +1,7 @@ from __future__ import division import numpy as np # noqa +import sys order = 4 @@ -20,7 +21,10 @@ def main(): draw_2d_mesh(mesh, set_bounding_box=True) import matplotlib.pyplot as pt - pt.show() + if sys.stdin.isatty(): + pt.show() + else: + pt.savefig("plot.pdf") if __name__ == "__main__": main() diff --git a/examples/simple-dg.py b/examples/simple-dg.py new file mode 100644 index 0000000000000000000000000000000000000000..8fe6f70600c3230bca4240b3c0808bf853ea650b --- /dev/null +++ b/examples/simple-dg.py @@ -0,0 +1,525 @@ +from __future__ import division, print_function + +__copyright__ = "Copyright (C) 2020 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 # noqa +import pyopencl as cl +import pyopencl.array as cla # noqa +import pyopencl.clmath as clmath +from pytools import memoize_method, memoize_in +from pytools.obj_array import ( + join_fields, make_obj_array, + with_object_array_or_scalar, + is_obj_array) +import loopy as lp +from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa + + +# Features lost vs. https://github.com/inducer/grudge: +# - dimension independence / differential geometry +# - overintegration +# - operator fusion +# - distributed-memory + + +def with_queue(queue, ary): + return with_object_array_or_scalar( + lambda x: x.with_queue(queue), ary) + + +def without_queue(ary): + return with_queue(None, ary) + + +# {{{ discretization + +def parametrization_derivative(queue, discr): + result = np.zeros((discr.ambient_dim, discr.dim), dtype=object) + for iambient in range(discr.ambient_dim): + for idim in range(discr.dim): + result[iambient, idim] = discr.num_reference_derivative( + queue, (idim,), discr.nodes()[iambient]) + + return result + + +class DGDiscretization: + def __init__(self, cl_ctx, mesh, order): + self.order = order + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory + self.group_factory = PolynomialWarpAndBlendGroupFactory(order=order) + self.volume_discr = Discretization(cl_ctx, mesh, self.group_factory) + + assert self.volume_discr.dim == 2 + + @property + def cl_context(self): + return self.volume_discr.cl_context + + @property + def dim(self): + return self.volume_discr.dim + + # {{{ discretizations/connections + + @memoize_method + def boundary_connection(self, boundary_tag): + from meshmode.discretization.connection import make_face_restriction + return make_face_restriction( + self.volume_discr, + self.group_factory, + boundary_tag=boundary_tag) + + @memoize_method + def interior_faces_connection(self): + from meshmode.discretization.connection import ( + make_face_restriction, FACE_RESTR_INTERIOR) + return make_face_restriction( + self.volume_discr, + self.group_factory, + FACE_RESTR_INTERIOR, + per_face_groups=False) + + @memoize_method + def opposite_face_connection(self): + from meshmode.discretization.connection import \ + make_opposite_face_connection + + return make_opposite_face_connection(self.interior_faces_connection()) + + @memoize_method + def all_faces_connection(self): + from meshmode.discretization.connection import ( + make_face_restriction, FACE_RESTR_ALL) + return make_face_restriction( + self.volume_discr, + self.group_factory, + FACE_RESTR_ALL, + per_face_groups=False) + + @memoize_method + def get_to_all_face_embedding(self, where): + from meshmode.discretization.connection import \ + make_face_to_all_faces_embedding + + faces_conn = self.get_connection("vol", where) + return make_face_to_all_faces_embedding( + faces_conn, self.get_discr("all_faces")) + + def get_connection(self, src, tgt): + src_tgt = (src, tgt) + + if src_tgt == ("vol", "int_faces"): + return self.interior_faces_connection() + elif src_tgt == ("vol", "all_faces"): + return self.all_faces_connection() + elif src_tgt == ("vol", BTAG_ALL): + return self.boundary_connection(tgt) + elif src_tgt == ("int_faces", "all_faces"): + return self.get_to_all_face_embedding(src) + elif src_tgt == (BTAG_ALL, "all_faces"): + return self.get_to_all_face_embedding(src) + else: + raise ValueError(f"locations '{src}'->'{tgt}' not understood") + + def interp(self, src, tgt, vec): + if is_obj_array(vec): + return with_object_array_or_scalar( + lambda el: self.interp(src, tgt, el), vec) + + return self.get_connection(src, tgt)(vec.queue, vec) + + def get_discr(self, where): + if where == "vol": + return self.volume_discr + elif where == "all_faces": + return self.all_faces_connection().to_discr + elif where == "int_faces": + return self.interior_faces_connection().to_discr + elif where == BTAG_ALL: + return self.boundary_connection(where).to_discr + else: + raise ValueError(f"location '{where}' not understood") + + # }}} + + @memoize_method + def parametrization_derivative(self): + with cl.CommandQueue(self.cl_context) as queue: + return without_queue( + parametrization_derivative(queue, self.volume_discr)) + + @memoize_method + def vol_jacobian(self): + with cl.CommandQueue(self.cl_context) as queue: + [a, b], [c, d] = with_queue(queue, self.parametrization_derivative()) + return (a*d-b*c).with_queue(None) + + @memoize_method + def inverse_parametrization_derivative(self): + with cl.CommandQueue(self.cl_context) as queue: + [a, b], [c, d] = with_queue(queue, self.parametrization_derivative()) + + result = np.zeros((2, 2), dtype=object) + det = a*d-b*c + result[0, 0] = d/det + result[0, 1] = -b/det + result[1, 0] = -c/det + result[1, 1] = a/det + + return without_queue(result) + + def zeros(self, queue): + return self.volume_discr.zeros(queue) + + def grad(self, vec): + ipder = self.inverse_parametrization_derivative() + + queue = vec.queue + dref = [ + self.volume_discr.num_reference_derivative( + queue, (idim,), vec).with_queue(queue) + for idim in range(self.volume_discr.dim)] + + return make_obj_array([ + sum(dref_i*ipder_i for dref_i, ipder_i in zip(dref, ipder[iambient])) + for iambient in range(self.volume_discr.ambient_dim)]) + + def div(self, vecs): + return sum( + self.grad(vec_i)[i] for i, vec_i in enumerate(vecs)) + + @memoize_method + def normal(self, where): + bdry_discr = self.get_discr(where) + + with cl.CommandQueue(self.cl_context) as queue: + ((a,), (b,)) = with_queue( + queue, parametrization_derivative(queue, bdry_discr)) + + nrm = 1/(a**2+b**2)**0.5 + return without_queue(join_fields(b*nrm, -a*nrm)) + + @memoize_method + def face_jacobian(self, where): + bdry_discr = self.get_discr(where) + + with cl.CommandQueue(self.cl_context) as queue: + ((a,), (b,)) = with_queue(queue, + parametrization_derivative(queue, bdry_discr)) + + return ((a**2 + b**2)**0.5).with_queue(None) + + @memoize_method + def get_inverse_mass_matrix(self, grp, dtype): + import modepy as mp + matrix = mp.inverse_mass_matrix( + grp.basis(), + grp.unit_nodes) + + with cl.CommandQueue(self.cl_context) as queue: + return (cla.to_device(queue, matrix) + .with_queue(None)) + + def inverse_mass(self, vec): + if is_obj_array(vec): + return with_object_array_or_scalar( + lambda el: self.inverse_mass(el), vec) + + @memoize_in(self, "elwise_linear_knl") + def knl(): + knl = lp.make_kernel( + """{[k,i,j]: + 0<=k