diff --git a/examples/advection/var-velocity.py b/examples/advection/var-velocity.py index ddc7535288cd82c1bbc56cb8decd9b0644cea73a..00e4f0c8496dced20a21e808e68986ecec1d5be2 100644 --- a/examples/advection/var-velocity.py +++ b/examples/advection/var-velocity.py @@ -1,17 +1,26 @@ -# Copyright (C) 2007 Andreas Kloeckner -# -# 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 . +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2017 Bogdan Enache" + +__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 @@ -37,20 +46,19 @@ def main(write_output=True, order=4): dim = 2 + resolution = 15 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, -0.5), b=(0.5, 0.5), - n=(15, 15), order=order) + n=(resolution, resolution), order=order) - dt_factor = 4 - h = 1/20 - - discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order) + dt_factor = 5 + h = 1/resolution sym_x = sym.nodes(2) advec_v = join_fields(-1*sym_x[1], sym_x[0]) / 2 - flux_type = "central" + flux_type = "upwind" source_center = np.array([0.1, 0.1]) source_width = 0.05 @@ -58,30 +66,46 @@ def main(write_output=True, order=4): sym_x = sym.nodes(2) sym_source_center_dist = sym_x - source_center - def f(x): + def f_gaussian(x): return sym.exp( -np.dot(sym_source_center_dist, sym_source_center_dist) / source_width**2) + def f_step(x): + return sym.If( + sym.Comparison( + np.dot(sym_source_center_dist, sym_source_center_dist), + "<", + (4*source_width)**2), + 1, 0) + def u_analytic(x): return 0 from grudge.models.advection import VariableCoefficientAdvectionOperator + from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory # noqa + + discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order, + quad_tag_to_group_factory={ + #"product": None, + "product": QuadratureSimplexGroupFactory(order=4*order) + }) - discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order) op = VariableCoefficientAdvectionOperator(2, advec_v, - inflow_u=u_analytic(sym.nodes(dim, sym.BTAG_ALL)), + u_analytic(sym.nodes(dim, sym.BTAG_ALL)), quad_tag="product", flux_type=flux_type) - bound_op = bind(discr, op.sym_operator()) + bound_op = bind( + discr, op.sym_operator(), + #debug_flags=["dump_sym_operator_stages"] + ) - u = bind(discr, f(sym.nodes(dim)))(queue, t=0) + u = bind(discr, f_gaussian(sym.nodes(dim)))(queue, t=0) def rhs(t, u): return bound_op(queue, t=t, u=u) - final_time = 2 - + final_time = 50 dt = dt_factor * h/order**2 nsteps = (final_time // dt) + 1 dt = final_time/nsteps + 1e-15 @@ -90,7 +114,7 @@ def main(write_output=True, order=4): dt_stepper = set_up_rk4("u", dt, u, rhs) from grudge.shortcuts import make_visualizer - vis = make_visualizer(discr, vis_order=order) + vis = make_visualizer(discr, vis_order=2*order) step = 0 @@ -98,10 +122,11 @@ def main(write_output=True, order=4): if isinstance(event, dt_stepper.StateComputed): step += 1 + if step % 30 == 0: + print(step) - #print(step, event.t, norm(queue, u=event.state_component[0])) - vis.write_vtk_file("fld-%04d.vtu" % step, - [("u", event.state_component)]) + vis.write_vtk_file("fld-%04d.vtu" % step, + [("u", event.state_component)]) if __name__ == "__main__": diff --git a/grudge/discretization.py b/grudge/discretization.py index 6f39762c00be545aa387253b9f39ea02f5dbeb8a..04cf43302a0da235a66e6329d0040ae86ceda761 100644 --- a/grudge/discretization.py +++ b/grudge/discretization.py @@ -1,6 +1,6 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2015-2017 Andreas Kloeckner, Bogdan Enache" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -47,17 +47,21 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): .. automethod :: zeros """ - def __init__(self, cl_ctx, mesh, order, quad_min_degrees=None): + def __init__(self, cl_ctx, mesh, order, quad_tag_to_group_factory=None): """ - :param quad_min_degrees: A mapping from quadrature tags to the degrees - to which the desired quadrature is supposed to be exact. + :param quad_min_degrees: A mapping from quadrature tags (typically + strings--but may be any hashable/comparable object) to a + :class:`meshmode.discretization.ElementGroupFactory` indicating with + which quadrature discretization the operations are to be carried out, + or *None* to indicate that operations with this quadrature tag should + be carried out with the standard volume discretization. """ - if quad_min_degrees is None: - quad_min_degrees = {} + if quad_tag_to_group_factory is None: + quad_tag_to_group_factory = {} self.order = order - self.quad_min_degrees = quad_min_degrees + self.quad_tag_to_group_factory = quad_tag_to_group_factory from meshmode.discretization import Discretization @@ -82,16 +86,26 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): if dd.is_volume(): if qtag is not sym.QTAG_NONE: - # FIXME - raise NotImplementedError("quadrature") + return self._quad_volume_discr(qtag) return self._volume_discr - elif dd.domain_tag is sym.FACE_RESTR_ALL: - return self._all_faces_discr(qtag) + if qtag is not sym.QTAG_NONE: + no_quad_discr = self.discr_from_dd(sym.DOFDesc(dd.domain_tag)) + + from meshmode.discretization import Discretization + return Discretization( + self._volume_discr.cl_context, + no_quad_discr.mesh, + self.group_factory_for_quadrature_tag(qtag)) + + assert qtag is sym.QTAG_NONE + + if dd.domain_tag is sym.FACE_RESTR_ALL: + return self._all_faces_volume_connection().to_discr elif dd.domain_tag is sym.FACE_RESTR_INTERIOR: - return self._interior_faces_discr(qtag) + return self._interior_faces_connection().to_discr elif dd.is_boundary(): - return self._boundary_discr(dd.domain_tag, qtag) + return self._boundary_connection(dd.domain_tag).to_discr else: raise ValueError("DOF desc tag not understood: " + str(dd)) @@ -100,18 +114,32 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): from_dd = sym.as_dofdesc(from_dd) to_dd = sym.as_dofdesc(to_dd) - if from_dd.quadrature_tag is not sym.QTAG_NONE: - raise ValueError("cannot interpolate *from* a " - "(non-interpolatory) quadrature grid") - to_qtag = to_dd.quadrature_tag + if ( + not from_dd.is_volume() + and from_dd.quadrature_tag == to_dd.quadrature_tag + and to_dd.domain_tag is sym.FACE_RESTR_ALL): + faces_conn = self.connection_from_dds( + sym.DOFDesc("vol"), + sym.DOFDesc(from_dd.domain_tag)) + + from meshmode.discretization.connection import \ + make_face_to_all_faces_embedding + + return make_face_to_all_faces_embedding( + faces_conn, self.discr_from_dd(to_dd), + self.discr_from_dd(from_dd)) + # {{{ simplify domain + qtag change into chained - if (from_dd.domain_tag != to_dd.domain_tag - and from_dd.quadrature_tag != to_dd.quadrature_tag): + if ( + from_dd.domain_tag != to_dd.domain_tag + and from_dd.quadrature_tag is sym.QTAG_NONE + and to_dd.quadrature_tag is not sym.QTAG_NONE): - from meshmode.connection import ChainedDiscretizationConnection + from meshmode.discretization.connection import \ + ChainedDiscretizationConnection intermediate_dd = sym.DOFDesc(to_dd.domain_tag) return ChainedDiscretizationConnection( [ @@ -130,8 +158,10 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): # {{{ generic to-quad - if (from_dd.domain_tag == to_dd.domain_tag - and from_dd.quadrature_tag != to_dd.quadrature_tag): + if ( + from_dd.domain_tag == to_dd.domain_tag + and from_dd.quadrature_tag is sym.QTAG_NONE + and to_dd.quadrature_tag is not sym.QTAG_NONE): from meshmode.discretization.connection.same_mesh import \ make_same_mesh_connection @@ -141,38 +171,30 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): # }}} + if from_dd.quadrature_tag is not sym.QTAG_NONE: + raise ValueError("cannot interpolate *from* a " + "(non-interpolatory) quadrature grid") + + assert to_qtag is sym.QTAG_NONE + if from_dd.is_volume(): if to_dd.domain_tag is sym.FACE_RESTR_ALL: - return self._all_faces_volume_connection(to_qtag) + return self._all_faces_volume_connection() if to_dd.domain_tag is sym.FACE_RESTR_INTERIOR: - return self._interior_faces_connection(to_qtag) + return self._interior_faces_connection() elif to_dd.is_boundary(): assert from_dd.quadrature_tag is sym.QTAG_NONE return self._boundary_connection(to_dd.domain_tag) + elif to_dd.is_volume(): + from meshmode.discretization.connection import \ + make_same_mesh_connection + to_discr = self._quad_volume_discr(to_dd.quadrature_tag) + from_discr = self._volume_discr + return make_same_mesh_connection(to_discr, from_discr) else: raise ValueError("cannot interpolate from volume to: " + str(to_dd)) - elif from_dd.domain_tag is sym.FACE_RESTR_INTERIOR: - if to_dd.domain_tag is sym.FACE_RESTR_ALL and to_qtag is sym.QTAG_NONE: - return self._all_faces_connection(None) - else: - raise ValueError( - "cannot interpolate from interior faces to: " - + str(to_dd)) - - elif from_dd.domain_tag is sym.FACE_RESTR_ALL: - if to_dd.domain_tag is sym.FACE_RESTR_ALL and to_qtag is sym.QTAG_NONE: - return self._all_faces_connection(None) - - elif from_dd.is_boundary(): - if to_dd.domain_tag is sym.FACE_RESTR_ALL and to_qtag is sym.QTAG_NONE: - return self._all_faces_connection(from_dd.domain_tag) - else: - raise ValueError( - "cannot interpolate from interior faces to: " - + str(to_dd)) - else: raise ValueError("cannot interpolate from: " + str(from_dd)) @@ -185,11 +207,10 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): quadrature_tag = sym.QTAG_NONE from meshmode.discretization.poly_element import \ - PolynomialWarpAndBlendGroupFactory, \ - QuadratureSimplexGroupFactory + PolynomialWarpAndBlendGroupFactory if quadrature_tag is not sym.QTAG_NONE: - return QuadratureSimplexGroupFactory(order=self.order) + return self.quad_tag_to_group_factory[quadrature_tag] else: return PolynomialWarpAndBlendGroupFactory(order=self.order) @@ -210,33 +231,17 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): self.group_factory_for_quadrature_tag(sym.QTAG_NONE), boundary_tag=boundary_tag) - @memoize_method - def _boundary_discr(self, boundary_tag, quadrature_tag=None): - if quadrature_tag is None: - quadrature_tag = sym.QTAG_NONE - - if quadrature_tag is sym.QTAG_NONE: - return self._boundary_connection(boundary_tag).to_discr - else: - no_quad_bdry_discr = self.boundary_discr(boundary_tag, sym.QTAG_NONE) - - from meshmode.discretization import Discretization - return Discretization( - self._volume_discr.cl_context, - no_quad_bdry_discr.mesh, - self.group_factory_for_quadrature_tag(quadrature_tag)) - # }}} # {{{ interior faces @memoize_method - def _interior_faces_connection(self, quadrature_tag=None): + 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_for_quadrature_tag(quadrature_tag), + self.group_factory_for_quadrature_tag(sym.QTAG_NONE), FACE_RESTR_INTERIOR, # FIXME: This will need to change as soon as we support @@ -244,32 +249,24 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): # types. per_face_groups=False) - def _interior_faces_discr(self, quadrature_tag=None): - return self._interior_faces_connection(quadrature_tag).to_discr - @memoize_method - def opposite_face_connection(self, quadrature_tag): - if quadrature_tag is not sym.QTAG_NONE: - # FIXME - raise NotImplementedError("quadrature") - + def opposite_face_connection(self): from meshmode.discretization.connection import \ make_opposite_face_connection - return make_opposite_face_connection( - self._interior_faces_connection(quadrature_tag)) + return make_opposite_face_connection(self._interior_faces_connection()) # }}} # {{{ all-faces @memoize_method - def _all_faces_volume_connection(self, quadrature_tag=None): + def _all_faces_volume_connection(self): from meshmode.discretization.connection import ( make_face_restriction, FACE_RESTR_ALL) return make_face_restriction( self._volume_discr, - self.group_factory_for_quadrature_tag(quadrature_tag), + self.group_factory_for_quadrature_tag(sym.QTAG_NONE), FACE_RESTR_ALL, # FIXME: This will need to change as soon as we support @@ -277,30 +274,6 @@ class DGDiscretizationWithBoundaries(DiscretizationBase): # types. per_face_groups=False) - def _all_faces_discr(self, quadrature_tag=None): - return self._all_faces_volume_connection(quadrature_tag).to_discr - - @memoize_method - def _all_faces_connection(self, boundary_tag): - """Return a - :class:`meshmode.discretization.connection.DiscretizationConnection` - that goes from either - :meth:`_interior_faces_discr` (if *boundary_tag* is None) - or - :meth:`_boundary_discr` (if *boundary_tag* is not None) - to a discretization containing all the faces of the volume - discretization. - """ - from meshmode.discretization.connection import \ - make_face_to_all_faces_embedding - - if boundary_tag is None: - faces_conn = self._interior_faces_connection() - else: - faces_conn = self._boundary_connection(boundary_tag) - - return make_face_to_all_faces_embedding(faces_conn, self._all_faces_discr()) - # }}} @property diff --git a/grudge/execution.py b/grudge/execution.py index e4aebe5ca18b495acf4e608ff9f4cdfd218f57b1..2da2e508cc8ddf116eb4ffb30ecfd3f8835f957b 100644 --- a/grudge/execution.py +++ b/grudge/execution.py @@ -1,6 +1,6 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2015-2017 Andreas Kloeckner, Bogdan Enache" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -198,36 +198,37 @@ class ExecutionMapper(mappers.Evaluator, knl = lp.make_kernel( """{[k,i,j]: 0<=kabc', weights, vand_inv_t, grad_vand) + # }}} @@ -322,8 +346,6 @@ class VandermondeOperator(ElementwiseLinearOperator): # }}} -# }}} - # {{{ mass operators @@ -338,9 +360,6 @@ class MassOperatorBase(Operator): if dd_out is None: dd_out = dd_in - if dd_out != dd_in: - raise ValueError("dd_out and dd_in must be identical") - super(MassOperatorBase, self).__init__(dd_in, dd_out) @@ -358,19 +377,30 @@ class RefMassOperatorBase(ElementwiseLinearOperator): class RefMassOperator(RefMassOperatorBase): @staticmethod - def matrix(element_group): - return element_group.mass_matrix() + def matrix(out_element_group, in_element_group): + if out_element_group == in_element_group: + return in_element_group.mass_matrix() + + from modepy import vandermonde + vand = vandermonde(out_element_group.basis(), out_element_group.unit_nodes) + o_vand = vandermonde(out_element_group.basis(), in_element_group.unit_nodes) + vand_inv_t = np.linalg.inv(vand).T + + weights = in_element_group.weights + + return np.einsum('c,bz,acz->abc', weights, vand_inv_t, o_vand) mapper_method = intern("map_ref_mass") class RefInverseMassOperator(RefMassOperatorBase): @staticmethod - def matrix(element_group): + def matrix(in_element_group, out_element_group): + assert in_element_group == out_element_group import modepy as mp return mp.inverse_mass_matrix( - element_group.basis(), - element_group.unit_nodes) + in_element_group.basis(), + in_element_group.unit_nodes) mapper_method = intern("map_ref_inverse_mass") @@ -405,7 +435,7 @@ class FaceMassOperatorBase(ElementwiseLinearOperator): if dd_in is None: dd_in = sym.DOFDesc(sym.FACE_RESTR_ALL, None) - if dd_out is None: + if dd_out is None or dd_out == "vol": dd_out = sym.DOFDesc("vol", sym.QTAG_NONE) if dd_out.uses_quadrature(): raise ValueError("face mass operator outputs are not on " @@ -476,10 +506,10 @@ def stiffness(dim): [StiffnessOperator(i) for i in range(dim)]) -def stiffness_t(dim): +def stiffness_t(dim, dd_in=None, dd_out=None): from pytools.obj_array import make_obj_array return make_obj_array( - [StiffnessTOperator(i) for i in range(dim)]) + [StiffnessTOperator(i, dd_in, dd_out) for i in range(dim)]) def integral(arg, dd=None): diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py index d64aef3a39f7927c80ccc85b48b5850d83c03bfb..00f7b8d386af6130810cf900bbaaebb0027f5adc 100644 --- a/grudge/symbolic/primitives.py +++ b/grudge/symbolic/primitives.py @@ -2,7 +2,7 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2008-2017 Andreas Kloeckner, Bogdan Enache" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -653,10 +653,18 @@ class TracePair: return 0.5*(self.int + self.ext) -def int_tpair(expression): - i = cse(_sym().interp("vol", "int_faces")(expression)) +def int_tpair(expression, qtag=None): + i = _sym().interp("vol", "int_faces")(expression) e = cse(_sym().OppositeInteriorFaceSwap()(i)) - return TracePair("int_faces", i, e) + + if qtag is not None and qtag != _sym().QTAG_NONE: + q_dd = _sym().DOFDesc("int_faces", qtag) + i = cse(_sym().interp("int_faces", q_dd)(i)) + e = cse(_sym().interp("int_faces", q_dd)(e)) + else: + q_dd = "int_faces" + + return TracePair(q_dd, i, e) def bdry_tpair(dd, interior, exterior): diff --git a/test/test_grudge.py b/test/test_grudge.py index 84c46168ede26efe1a42935501d69fba99b7700a..34532bfc80371e29bde28ea9674c48e36ab889aa 100644 --- a/test/test_grudge.py +++ b/test/test_grudge.py @@ -334,8 +334,7 @@ def test_convergence_advec(ctx_factory, mesh_name, mesh_pars, op_type, flux_type @pytest.mark.parametrize("order", [3, 4, 5]) -# test: 'test_convergence_advec(cl._csc, "disk", [0.1, 0.05], "strong", "upwind", 3)' -def test_convergence_maxwell(ctx_factory, order, visualize=False): +def test_convergence_maxwell(ctx_factory, order): """Test whether 3D maxwells actually converges""" cl_ctx = cl.create_some_context() @@ -403,6 +402,73 @@ def test_convergence_maxwell(ctx_factory, order, visualize=False): assert eoc_rec.order_estimate() > order +@pytest.mark.parametrize("order", [2, 3, 4]) +def test_improvement_quadrature(ctx_factory, order): + """Test whether quadrature improves things and converges""" + from meshmode.mesh.generation import generate_regular_rect_mesh + from grudge.models.advection import VariableCoefficientAdvectionOperator + from pytools.convergence import EOCRecorder + from pytools.obj_array import join_fields + from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory + + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + + dims = 2 + sym_nds = sym.nodes(dims) + advec_v = join_fields(-1*sym_nds[1], sym_nds[0]) + + flux = "upwind" + op = VariableCoefficientAdvectionOperator(2, advec_v, 0, flux_type=flux) + + def gaussian_mode(): + source_width = 0.1 + sym_x = sym.nodes(2) + return sym.exp(-np.dot(sym_x, sym_x) / source_width**2) + + def conv_test(descr, use_quad): + print("-"*75) + print(descr) + print("-"*75) + eoc_rec = EOCRecorder() + + ns = [20, 25] + for n in ns: + mesh = generate_regular_rect_mesh( + a=(-0.5,)*dims, + b=(0.5,)*dims, + n=(n,)*dims, + order=order) + + if use_quad: + quad_tag_to_group_factory = { + "product": QuadratureSimplexGroupFactory(order=4*order) + } + else: + quad_tag_to_group_factory = {"product": None} + + discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order, + quad_tag_to_group_factory=quad_tag_to_group_factory) + + bound_op = bind(discr, op.sym_operator()) + fields = bind(discr, gaussian_mode())(queue, t=0) + norm = bind(discr, sym.norm(2, sym.var("u"))) + + esc = bound_op(queue, u=fields) + total_error = norm(queue, u=esc) + eoc_rec.add_data_point(1.0/n, total_error) + + print(eoc_rec.pretty_print(abscissa_label="h", error_label="LInf Error")) + + return eoc_rec.order_estimate(), np.array([x[1] for x in eoc_rec.history]) + + eoc, errs = conv_test("no quadrature", False) + q_eoc, q_errs = conv_test("with quadrature", True) + assert q_eoc > eoc + assert (q_errs < errs).all() + assert q_eoc > order + + def test_foreign_points(ctx_factory): pytest.importorskip("sumpy") import sumpy.point_calculus as pc