From 5dfd2c8a2b403103137fa4dfea410f06eaec9ccb Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner <inform@tiker.net> Date: Tue, 6 Oct 2015 15:15:13 -0500 Subject: [PATCH] Towards a working example --- examples/wave/wave-min.py | 64 ++--- grudge/models/second_order.py | 509 ++++++++++++++++++++++++++++++++++ grudge/shortcuts.py | 39 +++ grudge/symbolic/tools.py | 11 +- 4 files changed, 579 insertions(+), 44 deletions(-) create mode 100644 grudge/models/second_order.py create mode 100644 grudge/shortcuts.py diff --git a/examples/wave/wave-min.py b/examples/wave/wave-min.py index 44aa23e..d6dd94f 100644 --- a/examples/wave/wave-min.py +++ b/examples/wave/wave-min.py @@ -2,7 +2,7 @@ from __future__ import division, print_function -__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -29,34 +29,31 @@ import numpy as np def main(write_output=True): - from math import sin, exp, sqrt # noqa + from meshmode.mesh.generation import generate_regular_rect_mesh + mesh = generate_regular_rect_mesh(a=(-0.5, -0.5), b=(0.5, 0.5)) - from grudge.mesh.generator import make_rect_mesh - mesh = make_rect_mesh(a=(-0.5, -0.5), b=(0.5, 0.5), max_area=0.008) + from grudge.shortcuts import make_discretization + discr = make_discretization(mesh, order=4) - from grudge.backends.jit import Discretization - - discr = Discretization(mesh, order=4) - - from grudge.visualization import VtkVisualizer - vis = VtkVisualizer(discr, None, "fld") + #from grudge.visualization import VtkVisualizer + #vis = VtkVisualizer(discr, None, "fld") source_center = np.array([0.1, 0.22]) source_width = 0.05 source_omega = 3 - import grudge.optemplate as sym + import grudge.symbolic as sym sym_x = sym.nodes(2) sym_source_center_dist = sym_x - source_center from grudge.models.wave import StrongWaveOperator from grudge.mesh import TAG_ALL, TAG_NONE op = StrongWaveOperator(-0.1, discr.dimensions, - source_f= - sym.CFunction("sin")(source_omega*sym.ScalarParameter("t")) - * sym.CFunction("exp")( - -np.dot(sym_source_center_dist, sym_source_center_dist) - / source_width**2), + source_f=( + sym.CFunction("sin")(source_omega*sym.ScalarParameter("t")) + * sym.CFunction("exp")( + -np.dot(sym_source_center_dist, sym_source_center_dist) + / source_width**2)), dirichlet_tag=TAG_NONE, neumann_tag=TAG_NONE, radiation_tag=TAG_ALL, @@ -75,31 +72,28 @@ def main(write_output=True): dt = op.estimate_timestep(discr, fields=fields) dt_stepper.set_up(t_start=0, dt_start=dt, context={"y": fields}) - nsteps = int(10/dt) - print "dt=%g nsteps=%d" % (dt, nsteps) + final_t = 10 + nsteps = int(final_t/dt) + print("dt=%g nsteps=%d" % (dt, nsteps)) - for event in interp.run(t_end=final_t): - if isinstance(event, interp.StateComputed): - assert event.component_id == component_id - values.append(event.state_component[0]) - times.append(event.t) + step = 0 - rhs = op.bind(discr) - for step in range(nsteps): - t = step*dt + for event in dt_stepper.run(t_end=final_t): + if isinstance(event, dt_stepper.StateComputed): + assert event.component_id == "y" - if step % 10 == 0 and write_output: - print step, t, discr.norm(fields[0]) - visf = vis.make_file("fld-%04d" % step) + step += 1 - vis.add_data(visf, - [("u", fields[0]), ("v", fields[1:]), ], - time=t, step=step) - visf.close() + # if step % 10 == 0 and write_output: + # print(step, event.t, discr.norm(fields[0])) + # visf = vis.make_file("fld-%04d" % step) - fields = stepper(fields, t, dt, rhs) + # vis.add_data(visf, + # [("u", fields[0]), ("v", fields[1:]), ], + # time=event.t, step=step) + # visf.close() - vis.close() + #vis.close() if __name__ == "__main__": diff --git a/grudge/models/second_order.py b/grudge/models/second_order.py new file mode 100644 index 0000000..56eff04 --- /dev/null +++ b/grudge/models/second_order.py @@ -0,0 +1,509 @@ +# -*- coding: utf8 -*- +"""Schemes for second-order derivatives.""" + +from __future__ import division + +__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 +import hedge.optemplate + + +# {{{ stabilization term generator + +class StabilizationTermGenerator(hedge.optemplate.IdentityMapper): + def __init__(self, flux_args): + hedge.optemplate.IdentityMapper.__init__(self) + self.flux_args = flux_args + self.flux_arg_lookup = dict( + (flux_arg, i) for i, flux_arg in enumerate(flux_args)) + + def get_flux_arg_idx(self, expr, quad_above): + from hedge.optemplate.mappers import QuadratureDetector + + quad_below = QuadratureDetector()(expr) + if quad_above: + if quad_below is not None: + # Both the part of the expression above and below the + # differentiation operator had quadrature upsamplers in it. + # Since we're removing the differentiation operator, there are + # now two layers of quadrature operators. We need to change the + # inner layer to be the only layer. + + from hedge.optemplate.mappers import QuadratureUpsamplerChanger + expr = QuadratureUpsamplerChanger(quad_above[0])(expr) + else: + # Only the part of the expression above the differentiation + # operator had quadrature. Insert quadrature here, be done. + expr = quad_above[0](expr) + else: + if quad_below is not None: + # Only the part of the expression below the differentiation + # operator had quadrature--the stuff above doesn't want it. + # Get rid of it. + from hedge.optemplate.mappers import QuadratureUpsamplerRemover + expr = QuadratureUpsamplerRemover({}, do_warn=False)(expr) + else: + # No quadrature, no headaches. + pass + + try: + return self.flux_arg_lookup[expr] + except KeyError: + flux_arg_idx = len(self.flux_args) + self.flux_arg_lookup[expr] = flux_arg_idx + self.flux_args.append(expr) + return flux_arg_idx + + def map_operator_binding(self, expr, quad_above=[]): + from hedge.optemplate.operators import ( + DiffOperatorBase, FluxOperatorBase, + InverseMassOperator, + QuadratureInteriorFacesGridUpsampler) + + if isinstance(expr.op, DiffOperatorBase): + flux_arg_idx = self.get_flux_arg_idx(expr.field, quad_above=quad_above) + + from hedge.optemplate import \ + WeakFormDiffOperatorBase, \ + StrongFormDiffOperatorBase + if isinstance(expr.op, WeakFormDiffOperatorBase): + factor = -1 + elif isinstance(expr.op, StrongFormDiffOperatorBase): + factor = 1 + else: + raise RuntimeError("unknown type of differentiation " + "operator encountered by stab term generator") + + from hedge.flux import Normal, FluxScalarPlaceholder + sph = FluxScalarPlaceholder(flux_arg_idx) + return (factor + * Normal(expr.op.xyz_axis) + * (sph.int - sph.ext)) + + elif isinstance(expr.op, FluxOperatorBase): + return 0 + elif isinstance(expr.op, InverseMassOperator): + return self.rec(expr.field, quad_above) + elif isinstance(expr.op, QuadratureInteriorFacesGridUpsampler): + if quad_above: + raise RuntimeError("double quadrature upsampler found " + "when generating stabilization term") + return self.rec(expr.field, [expr.op]) + else: + from hedge.optemplate.tools import pretty + raise ValueError("stabilization term generator doesn't know " + "what to do with '%s'" % pretty(expr)) + + def map_variable(self, expr, quad_above=[]): + from hedge.flux import FieldComponent + return FieldComponent( + self.get_flux_arg_idx(expr, quad_above), + is_interior=True) + + def map_subscript(self, expr, quad_above=[]): + from pymbolic.primitives import Variable + assert isinstance(expr.aggregate, Variable) + from hedge.flux import FieldComponent + return FieldComponent( + self.get_flux_arg_idx(expr, quad_above), + is_interior=True) + +# }}} + + +# {{{ neumann bc generator + +class NeumannBCGenerator(hedge.optemplate.IdentityMapper): + def __init__(self, tag, bc): + hedge.optemplate.IdentityMapper.__init__(self) + self.tag = tag + self.bc = bc + + def map_operator_binding(self, expr): + if isinstance(expr.op, hedge.optemplate.DiffOperatorBase): + from hedge.optemplate import \ + WeakFormDiffOperatorBase, \ + StrongFormDiffOperatorBase + if isinstance(expr.op, WeakFormDiffOperatorBase): + factor = -1 + elif isinstance(expr.op, StrongFormDiffOperatorBase): + factor = 1 + else: + raise RuntimeError("unknown type of differentiation " + "operator encountered by stab term generator") + + from hedge.optemplate import BoundaryNormalComponent + return (self.bc * factor * + BoundaryNormalComponent(self.tag, expr.op.xyz_axis)) + + elif isinstance(expr.op, hedge.optemplate.FluxOperatorBase): + return 0 + elif isinstance(expr.op, hedge.optemplate.InverseMassOperator): + return self.rec(expr.field) + else: + raise ValueError("neumann normal direction generator doesn't know " + "what to do with '%s'" % expr) + +# }}} + + +class IPDGDerivativeGenerator(hedge.optemplate.IdentityMapper): + def map_operator_binding(self, expr): + if isinstance(expr.op, hedge.optemplate.DiffOperatorBase): + from hedge.optemplate import ( + WeakFormDiffOperatorBase, + StrongFormDiffOperatorBase) + + if isinstance(expr.op, WeakFormDiffOperatorBase): + factor = -1 + elif isinstance(expr.op, StrongFormDiffOperatorBase): + factor = 1 + else: + raise RuntimeError("unknown type of differentiation " + "operator encountered by stab term generator") + + from hedge.optemplate import DifferentiationOperator + return factor*DifferentiationOperator(expr.op.xyz_axis)(expr.field) + + elif isinstance(expr.op, hedge.optemplate.FluxOperatorBase): + return 0 + elif isinstance(expr.op, hedge.optemplate.InverseMassOperator): + return self.rec(expr.field) + elif isinstance(expr.op, + hedge.optemplate.QuadratureInteriorFacesGridUpsampler): + return hedge.optemplate.IdentityMapper.map_operator_binding( + self, expr) + else: + from hedge.optemplate.tools import pretty + raise ValueError("IPDG derivative generator doesn't know " + "what to do with '%s'" % pretty(expr)) + + +# {{{ second derivative target + +class SecondDerivativeTarget(object): + def __init__(self, dimensions, strong_form, operand, + int_flux_operand=None, + bdry_flux_int_operand=None): + """ + :param int_flux_operand: if not None, is used as the interior + argument to the interior fluxes. This is useful e.g. if the boundary + values are on a quadrature grid--in this case, *bdry_flux_int_operand* + can be passed to also be on a boundary grid. If it is None, it defaults + to *operand*. + + :param bdry_flux_int_operand: if not None, is used as the interior + argument to the boundary fluxes. This is useful e.g. if the boundary + values are on a quadrature grid--in this case, *bdry_flux_int_operand* + can be passed to also be on a boundary grid. If it is None, it defaults + to *int_flux_operand*. + """ + self.dimensions = dimensions + self.operand = operand + + if int_flux_operand is None: + int_flux_operand = operand + if bdry_flux_int_operand is None: + bdry_flux_int_operand = int_flux_operand + + self.int_flux_operand = int_flux_operand + self.bdry_flux_int_operand = bdry_flux_int_operand + + self.strong_form = strong_form + if strong_form: + self.strong_neg = -1 + else: + self.strong_neg = 1 + + self.local_derivatives = 0 + self.inner_fluxes = 0 + self.boundary_fluxes = 0 + + def add_local_derivatives(self, expr): + self.local_derivatives = self.local_derivatives \ + + (-self.strong_neg)*expr + + def vec_times(self, vec, operand): + from pytools.obj_array import is_obj_array + + if is_obj_array(operand): + if len(operand) != self.dimensions: + raise ValueError("operand of vec_times must have %d dimensions" + % self.dimensions) + + return np.dot(vec, operand) + else: + return vec*operand + + def normal_times_flux(self, flux): + from hedge.flux import make_normal + return self.vec_times(make_normal(self.dimensions), flux) + + def apply_diff(self, nabla, operand): + from pytools.obj_array import make_obj_array, is_obj_array + if is_obj_array(operand): + if len(operand) != self.dimensions: + raise ValueError("operand of apply_diff must have %d dimensions" + % self.dimensions) + + return sum(nabla[i](operand[i]) for i in range(self.dimensions)) + else: + return make_obj_array( + [nabla[i](operand) for i in range(self.dimensions)]) + + def _local_nabla(self): + if self.strong_form: + from hedge.optemplate import make_stiffness + return make_stiffness(self.dimensions) + else: + from hedge.optemplate import make_stiffness_t + return make_stiffness_t(self.dimensions) + + def add_derivative(self, operand=None): + if operand is None: + operand = self.operand + + self.add_local_derivatives( + self.apply_diff(self._local_nabla(), operand)) + + def add_inner_fluxes(self, flux, expr): + from hedge.optemplate import get_flux_operator + self.inner_fluxes = self.inner_fluxes \ + + get_flux_operator(self.strong_neg*flux)(expr) + + def add_boundary_flux(self, flux, volume_expr, bdry_expr, tag): + from hedge.optemplate import BoundaryPair, get_flux_operator + self.boundary_fluxes = self.boundary_fluxes + \ + get_flux_operator(self.strong_neg*flux)(BoundaryPair( + volume_expr, bdry_expr, tag)) + + @property + def fluxes(self): + return self.inner_fluxes + self.boundary_fluxes + + @property + def all(self): + return self.local_derivatives + self.fluxes + + @property + def minv_all(self): + from hedge.optemplate.primitives import make_common_subexpression as cse + from hedge.optemplate.operators import InverseMassOperator + return (cse(InverseMassOperator()(self.local_derivatives), "grad_loc") + + cse(InverseMassOperator()(self.fluxes), "grad_flux")) + +# }}} + + +# {{{ second derivative schemes + +class SecondDerivativeBase(object): + def grad(self, tgt, bc_getter, dirichlet_tags, neumann_tags): + """ + :param bc_getter: a function (tag, volume_expr) -> boundary expr. + *volume_expr* will be None to query the Neumann condition. + """ + n_times = tgt.normal_times_flux + + if tgt.strong_form: + def adjust_flux(f): + return n_times(u.int) - f + else: + def adjust_flux(f): + return f + + from hedge.flux import FluxScalarPlaceholder + u = FluxScalarPlaceholder() + + tgt.add_derivative() + tgt.add_inner_fluxes( + adjust_flux(self.grad_interior_flux(tgt, u)), + tgt.int_flux_operand) + + for tag in dirichlet_tags: + tgt.add_boundary_flux( + adjust_flux(n_times(u.ext)), + tgt.bdry_flux_int_operand, bc_getter(tag, tgt.operand), tag) + + for tag in neumann_tags: + tgt.add_boundary_flux( + adjust_flux(n_times(u.int)), + tgt.bdry_flux_int_operand, 0, tag) + + def add_div_bcs(self, tgt, bc_getter, dirichlet_tags, neumann_tags, + stab_term, adjust_flux, flux_v, flux_arg_int, + grad_flux_arg_count): + from pytools.obj_array import make_obj_array, join_fields + n_times = tgt.normal_times_flux + + def unwrap_cse(expr): + from pymbolic.primitives import CommonSubexpression + if isinstance(expr, CommonSubexpression): + return expr.child + else: + return expr + + for tag in dirichlet_tags: + dir_bc_w = join_fields( + [0]*grad_flux_arg_count, + [bc_getter(tag, unwrap_cse(vol_expr)) for vol_expr in + flux_arg_int[grad_flux_arg_count:]]) + tgt.add_boundary_flux( + adjust_flux(n_times(flux_v.int-stab_term)), + flux_arg_int, dir_bc_w, tag) + + loc_bc_vec = make_obj_array([0]*len(flux_arg_int)) + + for tag in neumann_tags: + neu_bc_w = join_fields( + NeumannBCGenerator(tag, bc_getter(tag, None))(tgt.operand), + [0]*len(flux_arg_int)) + + tgt.add_boundary_flux( + adjust_flux(n_times(flux_v.ext)), + loc_bc_vec, neu_bc_w, tag) + + +class LDGSecondDerivative(SecondDerivativeBase): + def __init__(self, beta_value=0.5, stab_coefficient=1): + self.beta_value = beta_value + self.stab_coefficient = stab_coefficient + + def beta(self, tgt): + return np.array([self.beta_value]*tgt.dimensions, dtype=np.float64) + + def grad_interior_flux(self, tgt, u): + from hedge.optemplate.primitives import make_common_subexpression as cse + n_times = tgt.normal_times_flux + v_times = tgt.vec_times + + return n_times( + cse(u.avg, "u_avg") + - v_times(self.beta(tgt), n_times(u.int-u.ext))) + + def div(self, tgt, bc_getter, dirichlet_tags, neumann_tags): + """ + :param bc_getter: a function (tag, volume_expr) -> boundary expr. + *volume_expr* will be None to query the Neumann condition. + """ + + from hedge.optemplate.primitives import make_common_subexpression as cse + from hedge.flux import FluxVectorPlaceholder, PenaltyTerm + + n_times = tgt.normal_times_flux + v_times = tgt.vec_times + + if tgt.strong_form: + def adjust_flux(f): + return n_times(flux_v.int) - f + else: + def adjust_flux(f): + return f + + flux_v = FluxVectorPlaceholder(tgt.dimensions) + + stab_term_generator = StabilizationTermGenerator( + list(tgt.int_flux_operand)) + stab_term = (self.stab_coefficient * PenaltyTerm() + * stab_term_generator(tgt.int_flux_operand)) + + flux = n_times(flux_v.avg + + v_times(self.beta(tgt), + cse(n_times(flux_v.int - flux_v.ext), "jump_v")) + - stab_term) + + from pytools.obj_array import make_obj_array + flux_arg_int = cse(make_obj_array(stab_term_generator.flux_args)) + + tgt.add_derivative(cse(tgt.operand)) + tgt.add_inner_fluxes(adjust_flux(flux), flux_arg_int) + + self.add_div_bcs(tgt, bc_getter, dirichlet_tags, neumann_tags, + stab_term, adjust_flux, flux_v, flux_arg_int, tgt.dimensions) + + +class StabilizedCentralSecondDerivative(LDGSecondDerivative): + def __init__(self, stab_coefficient=1): + LDGSecondDerivative.__init__(self, 0, stab_coefficient=stab_coefficient) + + +class CentralSecondDerivative(LDGSecondDerivative): + def __init__(self): + LDGSecondDerivative.__init__(self, 0, 0) + + +class IPDGSecondDerivative(SecondDerivativeBase): + def __init__(self, stab_coefficient=1): + self.stab_coefficient = stab_coefficient + + def grad_interior_flux(self, tgt, u): + from hedge.optemplate.primitives import make_common_subexpression as cse + n_times = tgt.normal_times_flux + return n_times(cse(u.avg, "u_avg")) + + def div(self, tgt, bc_getter, dirichlet_tags, neumann_tags): + """ + :param bc_getter: a function (tag, volume_expr) -> boundary expr. + *volume_expr* will be None to query the Neumann condition. + """ + + from hedge.optemplate.primitives import make_common_subexpression as cse + from hedge.flux import FluxVectorPlaceholder, PenaltyTerm + + n_times = tgt.normal_times_flux + + if tgt.strong_form: + def adjust_flux(f): + return n_times(flux_v.int) - f + else: + def adjust_flux(f): + return f + + dim = tgt.dimensions + + flux_w = FluxVectorPlaceholder(2*tgt.dimensions) + flux_v = flux_w[:dim] + pure_diff_v = flux_w[dim:] + flux_args = ( + list(tgt.int_flux_operand) + + list(IPDGDerivativeGenerator()(tgt.int_flux_operand))) + + stab_term_generator = StabilizationTermGenerator(flux_args) + stab_term = (self.stab_coefficient * PenaltyTerm() + * stab_term_generator(tgt.int_flux_operand)) + flux = n_times(pure_diff_v.avg - stab_term) + + from pytools.obj_array import make_obj_array + flux_arg_int = cse(make_obj_array(stab_term_generator.flux_args)) + + tgt.add_derivative(cse(tgt.operand)) + tgt.add_inner_fluxes(adjust_flux(flux), flux_arg_int) + + self.add_div_bcs(tgt, bc_getter, dirichlet_tags, neumann_tags, + stab_term, adjust_flux, flux_v, flux_arg_int, 2*tgt.dimensions) + +# }}} + +# vim: fdm=marker diff --git a/grudge/shortcuts.py b/grudge/shortcuts.py new file mode 100644 index 0000000..9b20ee4 --- /dev/null +++ b/grudge/shortcuts.py @@ -0,0 +1,39 @@ +"""Minimal example of a grudge driver.""" + +from __future__ import division, print_function + +__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. +""" + + +def make_discretization(mesh, order, **kwargs): + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory + + cl_ctx = kwargs.pop("cl_ctx", None) + if cl_ctx is None: + import pyopencl as cl + cl_ctx = cl.create_some_context() + + return Discretization(cl_ctx, mesh, + PolynomialWarpAndBlendGroupFactory(order=order)) diff --git a/grudge/symbolic/tools.py b/grudge/symbolic/tools.py index b404d28..3599a74 100644 --- a/grudge/symbolic/tools.py +++ b/grudge/symbolic/tools.py @@ -1,9 +1,6 @@ """Operator templates: extra bits of functionality.""" -from __future__ import division -from __future__ import absolute_import -from six.moves import range -from functools import reduce +from __future__ import division, absolute_import __copyright__ = "Copyright (C) 2008 Andreas Kloeckner" @@ -28,18 +25,14 @@ THE SOFTWARE. """ +from six.moves import range, reduce import numpy as np import pymbolic.primitives # noqa -from pytools import MovedFunctionDeprecationWrapper from decorator import decorator # {{{ convenience functions for optemplate creation -make_vector_field = \ - MovedFunctionDeprecationWrapper(pymbolic.primitives.make_sym_vector) - - def get_flux_operator(flux): """Return a flux operator that can be multiplied with a volume field to obtain the interior fluxes -- GitLab