diff --git a/grudge/__init__.py b/grudge/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..38a84cd2918b14d6805bcf1506b0d1d8d40bc145 100644 --- a/grudge/__init__.py +++ b/grudge/__init__.py @@ -0,0 +1,26 @@ +from __future__ import division, absolute_import + +__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 grudge.symbolic.primitives as sym # noqa +import grudge.symbolic.flux.primitives as sym_flux # noqa diff --git a/grudge/models/wave.py b/grudge/models/wave.py index 67767565449e333bd15372362bd772fa548fe76d..a3a9aaf4448127c0bf4889f92adecef821966d9f 100644 --- a/grudge/models/wave.py +++ b/grudge/models/wave.py @@ -31,6 +31,7 @@ import numpy as np from grudge.models import HyperbolicOperator from grudge.second_order import CentralSecondDerivative from meshmode.mesh import BTAG_ALL, BTAG_NONE +from grudge import sym, sym_flux # {{{ constant-velocity @@ -79,15 +80,12 @@ class StrongWaveOperator(HyperbolicOperator): self.flux_type = flux_type def flux(self): - from grudge.flux import FluxVectorPlaceholder, make_normal - dim = self.dimensions - w = FluxVectorPlaceholder(1+dim) + w = sym_flux.FluxVectorPlaceholder(1+dim) u = w[0] v = w[1:] - normal = make_normal(dim) + normal = sym_flux.make_normal(dim) - from grudge.tools import join_fields flux_weak = join_fields( np.dot(v.avg, normal), u.avg * normal) diff --git a/grudge/symbolic/__init__.py b/grudge/symbolic/__init__.py index 900214e18a0fb1c79b5fe0f445163c18f71151ac..acd34e3532cba7516fab14dc1a9d2af46ddd649d 100644 --- a/grudge/symbolic/__init__.py +++ b/grudge/symbolic/__init__.py @@ -1,7 +1,6 @@ """Building blocks and mappers for operator expression trees.""" -from __future__ import division -from __future__ import absolute_import +from __future__ import division, absolute_import __copyright__ = "Copyright (C) 2008 Andreas Kloeckner" @@ -24,9 +23,3 @@ 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.symbolic.primitives import * # noqa -from grudge.symbolic.operators import * # noqa -from grudge.symbolic.mappers import * # noqa -from grudge.symbolic.tools import * # noqa diff --git a/grudge/symbolic/flux/__init__.py b/grudge/symbolic/flux/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/grudge/symbolic/flux/mappers.py b/grudge/symbolic/flux/mappers.py new file mode 100644 index 0000000000000000000000000000000000000000..1533274ae9041c5741a57f5d7390aab74673285f --- /dev/null +++ b/grudge/symbolic/flux/mappers.py @@ -0,0 +1,188 @@ +"""Building blocks for flux computation. Flux compilation.""" + +__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 pymbolic.mapper.collector +import pymbolic.mapper.flattener +import pymbolic.mapper.substitutor +import pymbolic.mapper.constant_folder +import pymbolic.mapper.flop_counter + + +class FluxIdentityMapperMixin(object): + def map_field_component(self, expr): + return expr + + def map_normal(self, expr): + # a leaf + return expr + + map_element_jacobian = map_normal + map_face_jacobian = map_normal + map_element_order = map_normal + map_local_mesh_size = map_normal + map_c_function = map_normal + + def map_scalar_parameter(self, expr): + return expr + + +class FluxIdentityMapper( + pymbolic.mapper.IdentityMapper, + FluxIdentityMapperMixin): + pass + + +class FluxSubstitutionMapper(pymbolic.mapper.substitutor.SubstitutionMapper, + FluxIdentityMapperMixin): + def map_field_component(self, expr): + result = self.subst_func(expr) + if result is not None: + return result + else: + return expr + + map_normal = map_field_component + + +class FluxStringifyMapper(pymbolic.mapper.stringifier.StringifyMapper): + def map_field_component(self, expr, enclosing_prec): + if expr.is_interior: + return "Int[%d]" % expr.index + else: + return "Ext[%d]" % expr.index + + def map_normal(self, expr, enclosing_prec): + return "Normal(%d)" % expr.axis + + def map_element_jacobian(self, expr, enclosing_prec): + return "ElJac" + + def map_face_jacobian(self, expr, enclosing_prec): + return "FJac" + + def map_element_order(self, expr, enclosing_prec): + return "N" + + def map_local_mesh_size(self, expr, enclosing_prec): + return "h" + + +class PrettyFluxStringifyMapper( + pymbolic.mapper.stringifier.CSESplittingStringifyMapperMixin, + FluxStringifyMapper): + pass + + +class FluxFlattenMapper(pymbolic.mapper.flattener.FlattenMapper, + FluxIdentityMapperMixin): + pass + + +class FluxDependencyMapper(pymbolic.mapper.dependency.DependencyMapper): + def map_field_component(self, expr): + return set([expr]) + + def map_normal(self, expr): + return set() + + map_element_jacobian = map_normal + map_face_jacobian = map_normal + map_element_order = map_normal + map_local_mesh_size = map_normal + map_c_function = map_normal + + def map_scalar_parameter(self, expr): + return set([expr]) + + +class FluxTermCollector(pymbolic.mapper.collector.TermCollector, + FluxIdentityMapperMixin): + pass + + +class FluxAllDependencyMapper(FluxDependencyMapper): + def map_normal(self, expr): + return set([expr]) + + def map_element_order(self, expr): + return set([expr]) + + def map_local_mesh_size(self, expr): + return set([expr]) + + +class FluxNormalizationMapper(pymbolic.mapper.collector.TermCollector, + FluxIdentityMapperMixin): + def get_dependencies(self, expr): + return FluxAllDependencyMapper()(expr) + + def map_constant_flux(self, expr): + if expr.local_c == expr.neighbor_c: + return expr.local_c + else: + return expr + + +class FluxCCFMapper(pymbolic.mapper.constant_folder.CommutativeConstantFoldingMapper, + FluxIdentityMapperMixin): + def is_constant(self, expr): + return not bool(FluxAllDependencyMapper()(expr)) + + +class FluxFlipper(FluxIdentityMapper): + def map_field_component(self, expr): + return expr.__class__(expr.index, not expr.is_interior) + + def map_normal(self, expr): + return -expr + + def map_element_jacobian(self, expr): + return expr.__class__(not expr.is_interior) + + map_element_order = map_element_jacobian + + +class FluxFlopCounter(pymbolic.mapper.flop_counter.FlopCounter): + def map_normal(self, expr): + return 0 + + map_element_jacobian = map_normal + map_face_jacobian = map_normal + map_element_order = map_normal + map_local_mesh_size = map_normal + + def map_field_component(self, expr): + return 0 + + def map_function_symbol(self, expr): + return 1 + + def map_c_function(self, expr): + return 1 + + def map_scalar_parameter(self, expr): + return 0 + +# vim: foldmethod=marker diff --git a/grudge/symbolic/flux/primitives b/grudge/symbolic/flux/primitives new file mode 100644 index 0000000000000000000000000000000000000000..11eb9218492868dbc21495e82c21da256e8356e4 --- /dev/null +++ b/grudge/symbolic/flux/primitives @@ -0,0 +1,256 @@ +"""Building blocks for flux computation. Flux compilation.""" + +__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 pymbolic.primitives + + +class Flux(pymbolic.primitives.AlgebraicLeaf): + def stringifier(self): + from grudge.symbolic.flux.mappers import FluxStringifyMapper + return FluxStringifyMapper + + +class FluxScalarParameter(pymbolic.primitives.Variable): + def __init__(self, name, is_complex=False): + pymbolic.primitives.Variable.__init__(self, name) + self.is_complex = is_complex + + def get_mapper_method(self, mapper): + return mapper.map_scalar_parameter + + +class FieldComponent(Flux): + def __init__(self, index, is_interior): + self.index = index + self.is_interior = is_interior + + def is_equal(self, other): + return (isinstance(other, FieldComponent) + and self.index == other.index + and self.is_interior == other.is_interior + ) + + def __getinitargs__(self): + return self.index, self.is_interior + + def get_hash(self): + return hash(( + self.__class__, + self.index, + self.is_interior)) + + def get_mapper_method(self, mapper): + return mapper.map_field_component + + +class Normal(Flux): + def __init__(self, axis): + self.axis = axis + + def __getinitargs__(self): + return self.axis, + + def is_equal(self, other): + return isinstance(other, Normal) and self.axis == other.axis + + def get_hash(self): + return hash(( + self.__class__, + self.axis)) + + def get_mapper_method(self, mapper): + return mapper.map_normal + + +class _StatelessFlux(Flux): + def __getinitargs__(self): + return () + + def is_equal(self, other): + return isinstance(other, self.__class__) + + def get_hash(self): + return hash(self.__class__) + + +class _SidedFlux(Flux): + def __init__(self, is_interior=True): + self.is_interior = is_interior + + def __getinitargs__(self): + return (self.is_interior,) + + def is_equal(self, other): + return (isinstance(other, self.__class__) + and self.is_interior == other.is_interior) + + def get_hash(self): + return hash((self.__class__, self.is_interior)) + + +class FaceJacobian(_StatelessFlux): + def get_mapper_method(self, mapper): + return mapper.map_face_jacobian + + +class ElementJacobian(_SidedFlux): + def get_mapper_method(self, mapper): + return mapper.map_element_jacobian + + +class ElementOrder(_SidedFlux): + def get_mapper_method(self, mapper): + return mapper.map_element_order + + +class LocalMeshSize(_StatelessFlux): + def get_mapper_method(self, mapper): + return mapper.map_local_mesh_size + + +def make_penalty_term(power=1): + from pymbolic.primitives import CommonSubexpression + return CommonSubexpression( + (ElementOrder()**2/LocalMeshSize())**power, + "penalty") + + +PenaltyTerm = make_penalty_term + + +class FluxFunctionSymbol(pymbolic.primitives.FunctionSymbol): + pass + + +class Abs(FluxFunctionSymbol): + arg_count = 1 + + +class Max(FluxFunctionSymbol): + arg_count = 2 + + +class Min(FluxFunctionSymbol): + arg_count = 2 + +flux_abs = Abs() +flux_max = Max() +flux_min = Min() + + +def norm(v): + return numpy.dot(v, v)**0.5 + + +def make_normal(dimensions): + return numpy.array([Normal(i) for i in range(dimensions)], dtype=object) + + +class FluxConstantPlaceholder(object): + def __init__(self, constant): + self.constant = constant + + @property + def int(self): + return self.constant + + @property + def ext(self): + return self.constant + + @property + def avg(self): + return self.constant + + +class FluxZeroPlaceholder(FluxConstantPlaceholder): + def __init__(self): + FluxConstantPlaceholder.__init__(self, 0) + + +class FluxScalarPlaceholder(object): + def __init__(self, component=0): + self.component = component + + def __str__(self): + return "FSP(%d)" % self.component + + @property + def int(self): + return FieldComponent(self.component, True) + + @property + def ext(self): + return FieldComponent(self.component, False) + + @property + def avg(self): + return 0.5*(self.int+self.ext) + + +class FluxVectorPlaceholder(object): + def __init__(self, components=None, scalars=None): + if not (components is not None or scalars is not None): + raise ValueError("either components or scalars must be specified") + if components is not None and scalars is not None: + raise ValueError("only one of components and scalars " + "may be specified") + + # make them arrays for the better indexing + if components: + self.scalars = numpy.array([ + FluxScalarPlaceholder(i) + for i in range(components)]) + else: + self.scalars = numpy.array(scalars) + + def __len__(self): + return len(self.scalars) + + def __str__(self): + return "%s(%s)" % (self.__class__.__name__, self.scalars) + + def __getitem__(self, idx): + if isinstance(idx, int): + return self.scalars[idx] + else: + return FluxVectorPlaceholder(scalars=self.scalars.__getitem__(idx)) + + @property + def int(self): + return numpy.array([scalar.int for scalar in self.scalars]) + + @property + def ext(self): + return numpy.array([scalar.ext for scalar in self.scalars]) + + @property + def avg(self): + return numpy.array([scalar.avg for scalar in self.scalars]) + +# }}} + +# vim: foldmethod=marker diff --git a/grudge/symbolic/flux/tools.py b/grudge/symbolic/flux/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..baaee7d5960e1fca2e4cc18e4ec689f42bc98220 --- /dev/null +++ b/grudge/symbolic/flux/tools.py @@ -0,0 +1,85 @@ +"""Flux creation helpers.""" + +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. +""" + + + + + +def make_lax_friedrichs_flux(wave_speed, state, fluxes, bdry_tags_states_and_fluxes, + strong): + from pytools.obj_array import join_fields + from grudge.flux import make_normal, FluxVectorPlaceholder, flux_max + + n = len(state) + d = len(fluxes) + normal = make_normal(d) + fvph = FluxVectorPlaceholder(len(state)*(1+d)+1) + + wave_speed_ph = fvph[0] + state_ph = fvph[1:1+n] + fluxes_ph = [fvph[1+i*n:1+(i+1)*n] for i in range(1, d+1)] + + penalty = flux_max(wave_speed_ph.int,wave_speed_ph.ext)*(state_ph.ext-state_ph.int) + + if not strong: + num_flux = 0.5*(sum(n_i*(f_i.int+f_i.ext) for n_i, f_i in zip(normal, fluxes_ph)) + - penalty) + else: + num_flux = 0.5*(sum(n_i*(f_i.int-f_i.ext) for n_i, f_i in zip(normal, fluxes_ph)) + + penalty) + + from grudge.optemplate import get_flux_operator + flux_op = get_flux_operator(num_flux) + int_operand = join_fields(wave_speed, state, *fluxes) + + from grudge.optemplate import BoundaryPair + return (flux_op(int_operand) + + sum( + flux_op(BoundaryPair(int_operand, + join_fields(0, bdry_state, *bdry_fluxes), tag)) + for tag, bdry_state, bdry_fluxes in bdry_tags_states_and_fluxes)) + + + + +def make_flux_bilinear_form(testee_expr, test_expr): + """Create a grudge flux expression for the bilinear form + + .. math:: + + \int_{\Gamma} u f(l_i^+,l_i^-), + + where :math:`u` is the (vector or scalar) unknown, given in *testee_epxr*, + :math:`f` is a function given by *test_expr* in terms of the Lagrange polynomials + :math:`l^_i^{\pm}` against which we are testing. In *test_expr*, :math:`l_i^{\pm}` + are represented by :class:`grudge.flux.FieldComponent(-1, True/False)`. + :math:`f` is required to be linear in :math:`l_i^{\pm}`. + """ + # keeping this stub to not lose the design work that went into its signature. + raise NotImplementedError + + + diff --git a/grudge/symbolic/mappers/__init__.py b/grudge/symbolic/mappers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5c88cdbbc92647213c0ab045be82e9ce062f7249 --- /dev/null +++ b/grudge/symbolic/mappers/__init__.py @@ -0,0 +1,1662 @@ +"""Operator template mappers.""" + +from __future__ import division + +__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. +""" + + +import numpy as np +import pymbolic.primitives +import pymbolic.mapper.stringifier +import pymbolic.mapper.evaluator +import pymbolic.mapper.dependency +import pymbolic.mapper.substitutor +import pymbolic.mapper.constant_folder +import pymbolic.mapper.flop_counter +from pymbolic.mapper import CSECachingMapperMixin + + +# {{{ mixins + +class LocalOpReducerMixin(object): + """Reduces calls to mapper methods for all local differentiation + operators to a single mapper method, and likewise for mass + operators. + """ + # {{{ global differentiation + def map_diff(self, expr, *args, **kwargs): + return self.map_diff_base(expr, *args, **kwargs) + + def map_minv_st(self, expr, *args, **kwargs): + return self.map_diff_base(expr, *args, **kwargs) + + def map_stiffness(self, expr, *args, **kwargs): + return self.map_diff_base(expr, *args, **kwargs) + + def map_stiffness_t(self, expr, *args, **kwargs): + return self.map_diff_base(expr, *args, **kwargs) + + def map_quad_stiffness_t(self, expr, *args, **kwargs): + return self.map_diff_base(expr, *args, **kwargs) + # }}} + + # {{{ global mass + def map_mass_base(self, expr, *args, **kwargs): + return self.map_elementwise_linear(expr, *args, **kwargs) + + def map_mass(self, expr, *args, **kwargs): + return self.map_mass_base(expr, *args, **kwargs) + + def map_inverse_mass(self, expr, *args, **kwargs): + return self.map_mass_base(expr, *args, **kwargs) + + def map_quad_mass(self, expr, *args, **kwargs): + return self.map_mass_base(expr, *args, **kwargs) + # }}} + + # {{{ reference differentiation + def map_ref_diff(self, expr, *args, **kwargs): + return self.map_ref_diff_base(expr, *args, **kwargs) + + def map_ref_stiffness_t(self, expr, *args, **kwargs): + return self.map_ref_diff_base(expr, *args, **kwargs) + + def map_ref_quad_stiffness_t(self, expr, *args, **kwargs): + return self.map_ref_diff_base(expr, *args, **kwargs) + # }}} + + # {{{ reference mass + def map_ref_mass_base(self, expr, *args, **kwargs): + return self.map_elementwise_linear(expr, *args, **kwargs) + + def map_ref_mass(self, expr, *args, **kwargs): + return self.map_ref_mass_base(expr, *args, **kwargs) + + def map_ref_inverse_mass(self, expr, *args, **kwargs): + return self.map_ref_mass_base(expr, *args, **kwargs) + + def map_ref_quad_mass(self, expr, *args, **kwargs): + return self.map_ref_mass_base(expr, *args, **kwargs) + # }}} + + +class FluxOpReducerMixin(object): + """Reduces calls to mapper methods for all flux + operators to a smaller number of mapper methods. + """ + def map_flux(self, expr, *args, **kwargs): + return self.map_flux_base(expr, *args, **kwargs) + + def map_bdry_flux(self, expr, *args, **kwargs): + return self.map_flux_base(expr, *args, **kwargs) + + def map_quad_flux(self, expr, *args, **kwargs): + return self.map_flux_base(expr, *args, **kwargs) + + def map_quad_bdry_flux(self, expr, *args, **kwargs): + return self.map_flux_base(expr, *args, **kwargs) + + +class OperatorReducerMixin(LocalOpReducerMixin, FluxOpReducerMixin): + """Reduces calls to *any* operator mapping function to just one.""" + def _map_op_base(self, expr, *args, **kwargs): + return self.map_operator(expr, *args, **kwargs) + + map_nodal_sum = _map_op_base + map_nodal_max = _map_op_base + map_nodal_min = _map_op_base + + map_diff_base = _map_op_base + map_ref_diff_base = _map_op_base + map_elementwise_linear = _map_op_base + map_flux_base = _map_op_base + map_elementwise_max = _map_op_base + map_boundarize = _map_op_base + map_flux_exchange = _map_op_base + map_quad_grid_upsampler = _map_op_base + map_quad_int_faces_grid_upsampler = _map_op_base + map_quad_bdry_grid_upsampler = _map_op_base + + +class CombineMapperMixin(object): + def map_operator_binding(self, expr): + return self.combine([self.rec(expr.op), self.rec(expr.field)]) + + def map_boundary_pair(self, expr): + return self.combine([self.rec(expr.field), self.rec(expr.bfield)]) + + +class IdentityMapperMixin(LocalOpReducerMixin, FluxOpReducerMixin): + def map_operator_binding(self, expr, *args, **kwargs): + assert not isinstance(self, BoundOpMapperMixin), \ + "IdentityMapper instances cannot be combined with " \ + "the BoundOpMapperMixin" + + return expr.__class__( + self.rec(expr.op, *args, **kwargs), + self.rec(expr.field, *args, **kwargs)) + + def map_boundary_pair(self, expr, *args, **kwargs): + assert not isinstance(self, BoundOpMapperMixin), \ + "IdentityMapper instances cannot be combined with " \ + "the BoundOpMapperMixin" + + return expr.__class__( + self.rec(expr.field, *args, **kwargs), + self.rec(expr.bfield, *args, **kwargs), + expr.tag) + + def map_elementwise_linear(self, expr, *args, **kwargs): + assert not isinstance(self, BoundOpMapperMixin), \ + "IdentityMapper instances cannot be combined with " \ + "the BoundOpMapperMixin" + + # it's a leaf--no changing children + return expr + + def map_scalar_parameter(self, expr, *args, **kwargs): + # it's a leaf--no changing children + return expr + + map_c_function = map_scalar_parameter + + map_ones = map_scalar_parameter + map_node_coordinate_component = map_scalar_parameter + map_normal_component = map_elementwise_linear + map_jacobian = map_scalar_parameter + map_inverse_metric_derivative = map_scalar_parameter + map_forward_metric_derivative = map_scalar_parameter + + map_nodal_sum = map_elementwise_linear + map_nodal_min = map_elementwise_linear + map_nodal_max = map_elementwise_linear + + map_mass_base = map_elementwise_linear + map_ref_mass_base = map_elementwise_linear + map_diff_base = map_elementwise_linear + map_ref_diff_base = map_elementwise_linear + map_flux_base = map_elementwise_linear + map_elementwise_max = map_elementwise_linear + map_boundarize = map_elementwise_linear + map_flux_exchange = map_elementwise_linear + map_quad_grid_upsampler = map_elementwise_linear + map_quad_int_faces_grid_upsampler = map_elementwise_linear + map_quad_bdry_grid_upsampler = map_elementwise_linear + + +class BoundOpMapperMixin(object): + def map_operator_binding(self, expr, *args, **kwargs): + return getattr(self, expr.op.mapper_method)( + expr.op, expr.field, *args, **kwargs) + +# }}} + + +# {{{ basic mappers + +class CombineMapper(CombineMapperMixin, pymbolic.mapper.CombineMapper): + pass + + +class DependencyMapper( + CombineMapperMixin, + pymbolic.mapper.dependency.DependencyMapper, + OperatorReducerMixin): + def __init__(self, + include_operator_bindings=True, + composite_leaves=None, + **kwargs): + if composite_leaves is False: + include_operator_bindings = False + if composite_leaves is True: + include_operator_bindings = True + + pymbolic.mapper.dependency.DependencyMapper.__init__(self, + composite_leaves=composite_leaves, **kwargs) + + self.include_operator_bindings = include_operator_bindings + + def map_operator_binding(self, expr): + if self.include_operator_bindings: + return set([expr]) + else: + return CombineMapperMixin.map_operator_binding(self, expr) + + def map_operator(self, expr): + return set() + + def map_scalar_parameter(self, expr): + return set([expr]) + + def _map_leaf(self, expr): + return set() + + map_ones = _map_leaf + + map_node_coordinate_component = _map_leaf + map_normal_component = _map_leaf + + map_jacobian = _map_leaf + map_forward_metric_derivative = _map_leaf + map_inverse_metric_derivative = _map_leaf + + +class FlopCounter( + CombineMapperMixin, + pymbolic.mapper.flop_counter.FlopCounter): + def map_operator_binding(self, expr): + return self.rec(expr.field) + + def map_scalar_parameter(self, expr): + return 0 + + def map_c_function(self, expr): + return 1 + + def map_ones(self, expr): + return 0 + + def map_node_coordinate_component(self, expr): + return 0 + + def map_normal_component(self, expr): + return 0 + + map_jacobian = map_normal_component + map_forward_metric_derivative = map_normal_component + map_inverse_metric_derivative = map_normal_component + + +class IdentityMapper( + IdentityMapperMixin, + pymbolic.mapper.IdentityMapper): + pass + + +class SubstitutionMapper(pymbolic.mapper.substitutor.SubstitutionMapper, + IdentityMapperMixin): + pass + + +class CSERemover(IdentityMapper): + def map_common_subexpression(self, expr): + return self.rec(expr.child) + +# }}} + + +# {{{ operator binder + +class OperatorBinder(CSECachingMapperMixin, IdentityMapper): + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_product(self, expr): + if len(expr.children) == 0: + return expr + + from pymbolic.primitives import flattened_product, Product + from grudge.optemplate import Operator, OperatorBinding + + first = expr.children[0] + if isinstance(first, Operator): + prod = flattened_product(expr.children[1:]) + if isinstance(prod, Product) and len(prod.children) > 1: + from warnings import warn + warn("Binding '%s' to more than one " + "operand in a product is ambiguous - " + "use the parenthesized form instead." + % first) + return OperatorBinding(first, self.rec(prod)) + else: + return first * self.rec(flattened_product(expr.children[1:])) + +# }}} + + +# {{{ operator specializer + +class OperatorSpecializer(CSECachingMapperMixin, IdentityMapper): + """Guided by a typedict obtained through type inference (i.e. by + :class:`grudge.optemplate.mappers.type_inference.TypeInferrrer`), + substitutes more specialized operators for generic ones. + + For example, if type inference has determined that a differentiation + operator is applied to an argument on a quadrature grid, this + differentiation operator is then swapped out for a *quadrature* + differentiation operator. + """ + + def __init__(self, typedict): + """ + :param typedict: generated by + :class:`grudge.optemplate.mappers.type_inference.TypeInferrer`. + """ + self.typedict = typedict + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_operator_binding(self, expr): + from grudge.optemplate.primitives import BoundaryPair + + from grudge.optemplate.operators import ( + MassOperator, + QuadratureMassOperator, + ReferenceMassOperator, + ReferenceQuadratureMassOperator, + + StiffnessTOperator, + QuadratureStiffnessTOperator, + ReferenceStiffnessTOperator, + ReferenceQuadratureStiffnessTOperator, + + QuadratureGridUpsampler, QuadratureBoundaryGridUpsampler, + FluxOperatorBase, FluxOperator, QuadratureFluxOperator, + BoundaryFluxOperator, QuadratureBoundaryFluxOperator, + BoundarizeOperator) + + from grudge.optemplate.mappers.type_inference import ( + type_info, QuadratureRepresentation) + + # {{{ figure out field type + try: + field_type = self.typedict[expr.field] + except TypeError: + # numpy arrays are not hashable + # has_quad_operand remains unset + + assert isinstance(expr.field, np.ndarray) + else: + try: + field_repr_tag = field_type.repr_tag + except AttributeError: + # boundary pairs are not assigned types + assert isinstance(expr.field, BoundaryPair) + has_quad_operand = False + else: + has_quad_operand = isinstance(field_repr_tag, + QuadratureRepresentation) + # }}} + + # Based on where this is run in the optemplate processing pipeline, + # we may encounter both reference and non-reference operators. + + # {{{ elementwise operators + if isinstance(expr.op, MassOperator) and has_quad_operand: + return QuadratureMassOperator( + field_repr_tag.quadrature_tag)(self.rec(expr.field)) + + elif isinstance(expr.op, ReferenceMassOperator) and has_quad_operand: + return ReferenceQuadratureMassOperator( + field_repr_tag.quadrature_tag)(self.rec(expr.field)) + + elif (isinstance(expr.op, StiffnessTOperator) and has_quad_operand): + return QuadratureStiffnessTOperator( + expr.op.xyz_axis, field_repr_tag.quadrature_tag)( + self.rec(expr.field)) + + elif (isinstance(expr.op, ReferenceStiffnessTOperator) + and has_quad_operand): + return ReferenceQuadratureStiffnessTOperator( + expr.op.xyz_axis, field_repr_tag.quadrature_tag)( + self.rec(expr.field)) + + elif (isinstance(expr.op, QuadratureGridUpsampler) + and isinstance(field_type, type_info.BoundaryVectorBase)): + # potential shortcut: + #if (isinstance(expr.field, OperatorBinding) + #and isinstance(expr.field.op, BoundarizeOperator)): + #return QuadratureBoundarizeOperator( + #expr.field.op.tag, expr.op.quadrature_tag)( + #self.rec(expr.field.field)) + + return QuadratureBoundaryGridUpsampler( + expr.op.quadrature_tag, field_type.boundary_tag)(expr.field) + # }}} + + elif isinstance(expr.op, BoundarizeOperator) and has_quad_operand: + raise TypeError("BoundarizeOperator cannot be applied to " + "quadrature-based operands--use QuadUpsample(Boundarize(...))") + + # {{{ flux operator specialization + elif isinstance(expr.op, FluxOperatorBase): + from pytools.obj_array import with_object_array_or_scalar + + repr_tag_cell = [None] + + def process_flux_arg(flux_arg): + arg_repr_tag = self.typedict[flux_arg].repr_tag + if repr_tag_cell[0] is None: + repr_tag_cell[0] = arg_repr_tag + else: + # An error for this condition is generated by + # the type inference pass. + + assert arg_repr_tag == repr_tag_cell[0] + + is_boundary = isinstance(expr.field, BoundaryPair) + if is_boundary: + bpair = expr.field + with_object_array_or_scalar(process_flux_arg, bpair.field) + with_object_array_or_scalar(process_flux_arg, bpair.bfield) + else: + with_object_array_or_scalar(process_flux_arg, expr.field) + + is_quad = isinstance(repr_tag_cell[0], QuadratureRepresentation) + if is_quad: + assert not expr.op.is_lift + quad_tag = repr_tag_cell[0].quadrature_tag + + new_fld = self.rec(expr.field) + flux = expr.op.flux + + if is_boundary: + if is_quad: + return QuadratureBoundaryFluxOperator( + flux, quad_tag, bpair.tag)(new_fld) + else: + return BoundaryFluxOperator(flux, bpair.tag)(new_fld) + else: + if is_quad: + return QuadratureFluxOperator(flux, quad_tag)(new_fld) + else: + return FluxOperator(flux, expr.op.is_lift)(new_fld) + # }}} + + else: + return IdentityMapper.map_operator_binding(self, expr) + + def map_normal_component(self, expr): + from grudge.optemplate.mappers.type_inference import ( + NodalRepresentation) + + expr_type = self.typedict[expr] + if not isinstance( + expr_type.repr_tag, + NodalRepresentation): + from grudge.optemplate.primitives import ( + BoundaryNormalComponent) + + # for now, parts of this are implemented. + raise NotImplementedError("normal components on quad. grids") + + return BoundaryNormalComponent( + expr.boundary_tag, expr.axis, + expr_type.repr_tag.quadrature_tag) + + # a leaf, doesn't change + return expr + +# }}} + + +# {{{ global-to-reference mapper + +class GlobalToReferenceMapper(CSECachingMapperMixin, IdentityMapper): + """Maps operators that apply on the global function space down to operators on + reference elements, together with explicit multiplication by geometric factors. + """ + + def __init__(self, dimensions): + CSECachingMapperMixin.__init__(self) + IdentityMapper.__init__(self) + + self.dimensions = dimensions + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_operator_binding(self, expr): + from grudge.optemplate.primitives import ( + Jacobian, InverseMetricDerivative) + + from grudge.optemplate.operators import ( + MassOperator, + ReferenceMassOperator, + QuadratureMassOperator, + ReferenceQuadratureMassOperator, + + StiffnessOperator, + + StiffnessTOperator, + ReferenceStiffnessTOperator, + QuadratureStiffnessTOperator, + ReferenceQuadratureStiffnessTOperator, + + InverseMassOperator, ReferenceInverseMassOperator, + DifferentiationOperator, ReferenceDifferentiationOperator, + + MInvSTOperator) + + # Global-to-reference is run after operator specialization, so + # if we encounter non-quadrature operators here, we know they + # must be nodal. + + def rewrite_derivative(ref_class, field, quadrature_tag=None, + with_jacobian=True): + if quadrature_tag is not None: + diff_kwargs = dict(quadrature_tag=quadrature_tag) + else: + diff_kwargs = {} + + rec_field = self.rec(field) + if with_jacobian: + rec_field = Jacobian(quadrature_tag) * rec_field + return sum(InverseMetricDerivative(None, rst_axis, expr.op.xyz_axis) * + ref_class(rst_axis, **diff_kwargs)(rec_field) + for rst_axis in range(self.dimensions)) + + if isinstance(expr.op, MassOperator): + return ReferenceMassOperator()( + Jacobian(None) * self.rec(expr.field)) + + elif isinstance(expr.op, QuadratureMassOperator): + return ReferenceQuadratureMassOperator( + expr.op.quadrature_tag)( + Jacobian(expr.op.quadrature_tag) * self.rec(expr.field)) + + elif isinstance(expr.op, InverseMassOperator): + return ReferenceInverseMassOperator()( + 1/Jacobian(None) * self.rec(expr.field)) + + elif isinstance(expr.op, StiffnessOperator): + return ReferenceMassOperator()(Jacobian(None) * + self.rec( + DifferentiationOperator(expr.op.xyz_axis)(expr.field))) + + elif isinstance(expr.op, DifferentiationOperator): + return rewrite_derivative( + ReferenceDifferentiationOperator, + expr.field, with_jacobian=False) + + elif isinstance(expr.op, StiffnessTOperator): + return rewrite_derivative( + ReferenceStiffnessTOperator, + expr.field) + + elif isinstance(expr.op, QuadratureStiffnessTOperator): + return rewrite_derivative( + ReferenceQuadratureStiffnessTOperator, + expr.field, quadrature_tag=expr.op.quadrature_tag) + + elif isinstance(expr.op, MInvSTOperator): + return self.rec( + InverseMassOperator()( + StiffnessTOperator(expr.op.xyz_axis)(expr.field))) + + else: + return IdentityMapper.map_operator_binding(self, expr) + +# }}} + + +# {{{ stringification --------------------------------------------------------- + +class StringifyMapper(pymbolic.mapper.stringifier.StringifyMapper): + def _format_btag(self, tag): + from grudge.mesh import SYSTEM_TAGS + if tag in SYSTEM_TAGS: + return tag.__name__ + else: + return repr(tag) + + def __init__(self, constant_mapper=str, flux_stringify_mapper=None): + pymbolic.mapper.stringifier.StringifyMapper.__init__( + self, constant_mapper=constant_mapper) + + if flux_stringify_mapper is None: + from grudge.flux import FluxStringifyMapper + flux_stringify_mapper = FluxStringifyMapper() + + self.flux_stringify_mapper = flux_stringify_mapper + + def map_boundary_pair(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "BPair(%s, %s, %s)" % ( + self.rec(expr.field, PREC_NONE), + self.rec(expr.bfield, PREC_NONE), + self._format_btag(expr.tag)) + + # {{{ nodal ops + + def map_nodal_sum(self, expr, enclosing_prec): + return "NodalSum" + + def map_nodal_max(self, expr, enclosing_prec): + return "NodalMax" + + def map_nodal_min(self, expr, enclosing_prec): + return "NodalMin" + + # }}} + + # {{{ global differentiation + + def map_diff(self, expr, enclosing_prec): + return "Diffx%d" % expr.xyz_axis + + def map_minv_st(self, expr, enclosing_prec): + return "MInvSTx%d" % expr.xyz_axis + + def map_stiffness(self, expr, enclosing_prec): + return "Stiffx%d" % expr.xyz_axis + + def map_stiffness_t(self, expr, enclosing_prec): + return "StiffTx%d" % expr.xyz_axis + + def map_quad_stiffness_t(self, expr, enclosing_prec): + return "Q[%s]StiffTx%d" % ( + expr.quadrature_tag, expr.xyz_axis) + # }}} + + # {{{ global mass + + def map_mass(self, expr, enclosing_prec): + return "M" + + def map_inverse_mass(self, expr, enclosing_prec): + return "InvM" + + def map_quad_mass(self, expr, enclosing_prec): + return "Q[%s]M" % expr.quadrature_tag + + # }}} + + # {{{ reference differentiation + def map_ref_diff(self, expr, enclosing_prec): + return "Diffr%d" % expr.rst_axis + + def map_ref_stiffness_t(self, expr, enclosing_prec): + return "StiffTr%d" % expr.rst_axis + + def map_ref_quad_stiffness_t(self, expr, enclosing_prec): + return "Q[%s]StiffTr%d" % ( + expr.quadrature_tag, expr.rst_axis) + # }}} + + # {{{ reference mass + + def map_ref_mass(self, expr, enclosing_prec): + return "RefM" + + def map_ref_inverse_mass(self, expr, enclosing_prec): + return "RefInvM" + + def map_ref_quad_mass(self, expr, enclosing_prec): + return "RefQ[%s]M" % expr.quadrature_tag + + # }}} + + def map_elementwise_linear(self, expr, enclosing_prec): + return "ElWLin:%s" % expr.__class__.__name__ + + # {{{ flux + + def map_flux(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "%s(%s)" % ( + expr.get_flux_or_lift_text(), + self.flux_stringify_mapper(expr.flux, PREC_NONE)) + + def map_bdry_flux(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "B[%s]%s(%s)" % ( + self._format_btag(expr.boundary_tag), + expr.get_flux_or_lift_text(), + self.flux_stringify_mapper(expr.flux, PREC_NONE)) + + def map_quad_flux(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "Q[%s]%s(%s)" % ( + expr.quadrature_tag, + expr.get_flux_or_lift_text(), + self.flux_stringify_mapper(expr.flux, PREC_NONE)) + + def map_quad_bdry_flux(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "Q[%s]B[%s]%s(%s)" % ( + expr.quadrature_tag, + self._format_btag(expr.boundary_tag), + expr.get_flux_or_lift_text(), + self.flux_stringify_mapper(expr.flux, PREC_NONE)) + + def map_whole_domain_flux(self, expr, enclosing_prec): + # used from grudge.backends.cuda.optemplate + if expr.is_lift: + opname = "WLift" + else: + opname = "WFlux" + + from pymbolic.mapper.stringifier import PREC_NONE + return "%s(%s)" % (opname, + self.rec(expr.rebuild_optemplate(), PREC_NONE)) + # }}} + + def map_elementwise_max(self, expr, enclosing_prec): + return "ElWMax" + + def map_boundarize(self, expr, enclosing_prec): + return "Boundarize<tag=%s>" % expr.tag + + def map_flux_exchange(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "FExch<idx=%s,rank=%d>(%s)" % (expr.index, expr.rank, + ", ".join(self.rec(arg, PREC_NONE) for arg in expr.arg_fields)) + + def map_ones(self, expr, enclosing_prec): + if expr.quadrature_tag is None: + return "Ones" + else: + return "Q[%s]Ones" % expr.quadrature_tag + + # {{{ geometry data + + def map_node_coordinate_component(self, expr, enclosing_prec): + if expr.quadrature_tag is None: + return "x[%d]" % expr.axis + else: + return ("Q[%s]x[%d]" % (expr.quadrature_tag, expr.axis)) + + def map_normal_component(self, expr, enclosing_prec): + if expr.quadrature_tag is None: + return ("Normal<tag=%s>[%d]" + % (expr.boundary_tag, expr.axis)) + else: + return ("Q[%s]Normal<tag=%s>[%d]" + % (expr.quadrature_tag, expr.boundary_tag, expr.axis)) + + def map_jacobian(self, expr, enclosing_prec): + if expr.quadrature_tag is None: + return "Jac" + else: + return "JacQ[%s]" % expr.quadrature_tag + + def map_forward_metric_derivative(self, expr, enclosing_prec): + result = "dx%d/dr%d" % (expr.xyz_axis, expr.rst_axis) + if expr.quadrature_tag is not None: + result += "Q[%s]" % expr.quadrature_tag + return result + + def map_inverse_metric_derivative(self, expr, enclosing_prec): + result = "dr%d/dx%d" % (expr.rst_axis, expr.xyz_axis) + if expr.quadrature_tag is not None: + result += "Q[%s]" % expr.quadrature_tag + return result + + # }}} + + def map_operator_binding(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "<%s>(%s)" % ( + self.rec(expr.op, PREC_NONE), + self.rec(expr.field, PREC_NONE)) + + def map_c_function(self, expr, enclosing_prec): + return expr.name + + def map_scalar_parameter(self, expr, enclosing_prec): + return "ScalarPar[%s]" % expr.name + + def map_quad_grid_upsampler(self, expr, enclosing_prec): + return "ToQ[%s]" % expr.quadrature_tag + + def map_quad_int_faces_grid_upsampler(self, expr, enclosing_prec): + return "ToIntFaceQ[%s]" % expr.quadrature_tag + + def map_quad_bdry_grid_upsampler(self, expr, enclosing_prec): + return "ToBdryQ[%s,%s]" % (expr.quadrature_tag, expr.boundary_tag) + + +class PrettyStringifyMapper( + pymbolic.mapper.stringifier.CSESplittingStringifyMapperMixin, + StringifyMapper): + def __init__(self): + pymbolic.mapper.stringifier.CSESplittingStringifyMapperMixin.__init__(self) + StringifyMapper.__init__(self) + + self.flux_to_number = {} + self.flux_string_list = [] + + self.bc_to_number = {} + self.bc_string_list = [] + + from grudge.flux import PrettyFluxStringifyMapper + self.flux_stringify_mapper = PrettyFluxStringifyMapper() + + def get_flux_number(self, flux): + try: + return self.flux_to_number[flux] + except KeyError: + from pymbolic.mapper.stringifier import PREC_NONE + str_flux = self.flux_stringify_mapper(flux, PREC_NONE) + + flux_number = len(self.flux_to_number) + self.flux_string_list.append(str_flux) + self.flux_to_number[flux] = flux_number + return flux_number + + def map_boundary_pair(self, expr, enclosing_prec): + try: + bc_number = self.bc_to_number[expr] + except KeyError: + from pymbolic.mapper.stringifier import PREC_NONE + str_bc = StringifyMapper.map_boundary_pair(self, expr, PREC_NONE) + + bc_number = len(self.bc_to_number) + self.bc_string_list.append(str_bc) + self.bc_to_number[expr] = bc_number + + return "BC%d@%s" % (bc_number, expr.tag) + + def map_operator_binding(self, expr, enclosing_prec): + from grudge.optemplate import BoundarizeOperator + if isinstance(expr.op, BoundarizeOperator): + from pymbolic.mapper.stringifier import PREC_CALL, PREC_SUM + return self.parenthesize_if_needed( + "%s@%s" % ( + self.rec(expr.field, PREC_CALL), + expr.op.tag), + enclosing_prec, PREC_SUM) + else: + return StringifyMapper.map_operator_binding( + self, expr, enclosing_prec) + + def get_bc_strings(self): + return ["BC%d : %s" % (i, bc_str) + for i, bc_str in enumerate(self.bc_string_list)] + + def get_flux_strings(self): + return ["Flux%d : %s" % (i, flux_str) + for i, flux_str in enumerate(self.flux_string_list)] + + def map_flux(self, expr, enclosing_prec): + return "%s%d" % ( + expr.get_flux_or_lift_text(), + self.get_flux_number(expr.flux)) + + def map_bdry_flux(self, expr, enclosing_prec): + return "B[%s]%s%d" % ( + expr.boundary_tag, + expr.get_flux_or_lift_text(), + self.get_flux_number(expr.flux)) + + def map_quad_flux(self, expr, enclosing_prec): + return "Q[%s]%s%d" % ( + expr.quadrature_tag, + expr.get_flux_or_lift_text(), + self.get_flux_number(expr.flux)) + + def map_quad_bdry_flux(self, expr, enclosing_prec): + return "Q[%s]B[%s]%s%d" % ( + expr.quadrature_tag, + expr.boundary_tag, + expr.get_flux_or_lift_text(), + self.get_flux_number(expr.flux)) + + +class NoCSEStringifyMapper(StringifyMapper): + def map_common_subexpression(self, expr, enclosing_prec): + return self.rec(expr.child, enclosing_prec) + +# }}} + + +# {{{ quadrature support + +class QuadratureUpsamplerRemover(CSECachingMapperMixin, IdentityMapper): + def __init__(self, quad_min_degrees, do_warn=True): + IdentityMapper.__init__(self) + CSECachingMapperMixin.__init__(self) + self.quad_min_degrees = quad_min_degrees + self.do_warn = do_warn + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_operator_binding(self, expr): + from grudge.optemplate.operators import ( + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler, + QuadratureBoundaryGridUpsampler) + + if isinstance(expr.op, (QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler, + QuadratureBoundaryGridUpsampler)): + try: + min_degree = self.quad_min_degrees[expr.op.quadrature_tag] + except KeyError: + if self.do_warn: + from warnings import warn + warn("No minimum degree for quadrature tag '%s' specified--" + "falling back to nodal evaluation" + % expr.op.quadrature_tag) + return self.rec(expr.field) + else: + if min_degree is None: + return self.rec(expr.field) + else: + return expr.op(self.rec(expr.field)) + else: + return IdentityMapper.map_operator_binding(self, expr) + + +class QuadratureDetector(CSECachingMapperMixin, CombineMapper): + """For a given expression, this mapper returns the upsampling + operator in effect at the root of the expression, or *None* + if there isn't one. + """ + class QuadStatusNotKnown: + pass + + map_common_subexpression_uncached = \ + CombineMapper.map_common_subexpression + + def combine(self, values): + from pytools import single_valued + return single_valued([ + v for v in values if v is not self.QuadStatusNotKnown]) + + def map_variable(self, expr): + return None + + def map_constant(self, expr): + return self.QuadStatusNotKnown + + def map_operator_binding(self, expr): + from grudge.optemplate.operators import ( + DiffOperatorBase, FluxOperatorBase, + MassOperatorBase, + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler) + + if isinstance(expr.op, ( + DiffOperatorBase, FluxOperatorBase, + MassOperatorBase)): + return None + elif isinstance(expr.op, (QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler)): + return expr.op + else: + return CombineMapper.map_operator_binding(self, expr) + + +class QuadratureUpsamplerChanger(CSECachingMapperMixin, IdentityMapper): + """This mapper descends the expression tree, down to each + quadrature-consuming operator (diff, mass) along each branch. + It then change + """ + def __init__(self, desired_quad_op): + IdentityMapper.__init__(self) + CSECachingMapperMixin.__init__(self) + + self.desired_quad_op = desired_quad_op + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_operator_binding(self, expr): + from grudge.optemplate.operators import ( + DiffOperatorBase, FluxOperatorBase, + MassOperatorBase, + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler) + + if isinstance(expr.op, ( + DiffOperatorBase, FluxOperatorBase, + MassOperatorBase)): + return expr + elif isinstance(expr.op, (QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler)): + return self.desired_quad_op(expr.field) + else: + return IdentityMapper.map_operator_binding(self, expr) + +# }}} + + +# {{{ simplification / optimization + +class CommutativeConstantFoldingMapper( + pymbolic.mapper.constant_folder.CommutativeConstantFoldingMapper, + IdentityMapperMixin): + + def __init__(self): + pymbolic.mapper.constant_folder\ + .CommutativeConstantFoldingMapper.__init__(self) + self.dep_mapper = DependencyMapper() + + def is_constant(self, expr): + return not bool(self.dep_mapper(expr)) + + def map_operator_binding(self, expr): + field = self.rec(expr.field) + + from grudge.tools import is_zero + if is_zero(field): + return 0 + + from grudge.optemplate.operators import FluxOperatorBase + from grudge.optemplate.primitives import BoundaryPair + + if isinstance(expr.op, FluxOperatorBase): + if isinstance(field, BoundaryPair): + return self.remove_zeros_from_boundary_flux(expr.op, field) + else: + return self.remove_zeros_from_interior_flux(expr.op, field) + else: + return expr.op(field) + + # {{{ remove zeros from interior flux + def remove_zeros_from_interior_flux(self, op, vol_field): + from pytools.obj_array import is_obj_array + if not is_obj_array(vol_field): + vol_field = [vol_field] + + from grudge.flux import FieldComponent + subst_map = {} + + from grudge.tools import is_zero, make_obj_array + + # process volume field + new_vol_field = [] + new_idx = 0 + for i, flux_arg in enumerate(vol_field): + flux_arg = self.rec(flux_arg) + + if is_zero(flux_arg): + subst_map[FieldComponent(i, is_interior=True)] = 0 + subst_map[FieldComponent(i, is_interior=False)] = 0 + else: + new_vol_field.append(flux_arg) + subst_map[FieldComponent(i, is_interior=True)] = \ + FieldComponent(new_idx, is_interior=True) + subst_map[FieldComponent(i, is_interior=False)] = \ + FieldComponent(new_idx, is_interior=False) + new_idx += 1 + + # substitute results into flux + def sub_flux(expr): + return subst_map.get(expr, expr) + + from grudge.flux import FluxSubstitutionMapper + new_flux = FluxSubstitutionMapper(sub_flux)(op.flux) + + if is_zero(new_flux): + return 0 + else: + return type(op)(new_flux, *op.__getinitargs__()[1:])( + make_obj_array(new_vol_field)) + + # }}} + + # {{{ remove zeros from boundary flux + def remove_zeros_from_boundary_flux(self, op, bpair): + vol_field = bpair.field + bdry_field = bpair.bfield + from pytools.obj_array import is_obj_array + if not is_obj_array(vol_field): + vol_field = [vol_field] + if not is_obj_array(bdry_field): + bdry_field = [bdry_field] + + from grudge.flux import FieldComponent + subst_map = {} + + # process volume field + from grudge.tools import is_zero, make_obj_array + new_vol_field = [] + new_idx = 0 + for i, flux_arg in enumerate(vol_field): + fc = FieldComponent(i, is_interior=True) + flux_arg = self.rec(flux_arg) + + if is_zero(flux_arg): + subst_map[fc] = 0 + else: + new_vol_field.append(flux_arg) + subst_map[fc] = FieldComponent(new_idx, is_interior=True) + new_idx += 1 + + # process boundary field + new_bdry_field = [] + new_idx = 0 + for i, flux_arg in enumerate(bdry_field): + fc = FieldComponent(i, is_interior=False) + flux_arg = self.rec(flux_arg) + + if is_zero(flux_arg): + subst_map[fc] = 0 + else: + new_bdry_field.append(flux_arg) + subst_map[fc] = FieldComponent(new_idx, is_interior=False) + new_idx += 1 + + # substitute results into flux + def sub_flux(expr): + return subst_map.get(expr, expr) + + from grudge.flux import FluxSubstitutionMapper + new_flux = FluxSubstitutionMapper(sub_flux)(op.flux) + + if is_zero(new_flux): + return 0 + else: + from grudge.optemplate.primitives import BoundaryPair + return type(op)(new_flux, *op.__getinitargs__()[1:])( + BoundaryPair( + make_obj_array(new_vol_field), + make_obj_array(new_bdry_field), + bpair.tag)) + + # }}} + + +class EmptyFluxKiller(CSECachingMapperMixin, IdentityMapper): + def __init__(self, mesh): + IdentityMapper.__init__(self) + self.mesh = mesh + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_operator_binding(self, expr): + from grudge.optemplate import BoundaryFluxOperatorBase + + if (isinstance(expr.op, BoundaryFluxOperatorBase) and + len(self.mesh.tag_to_boundary.get(expr.op.boundary_tag, [])) == 0): + return 0 + else: + return IdentityMapper.map_operator_binding(self, expr) + + +class _InnerDerivativeJoiner(pymbolic.mapper.RecursiveMapper): + def map_operator_binding(self, expr, derivatives): + from grudge.optemplate import DifferentiationOperator + + if isinstance(expr.op, DifferentiationOperator): + derivatives.setdefault(expr.op, []).append(expr.field) + return 0 + else: + return DerivativeJoiner()(expr) + + def map_common_subexpression(self, expr, derivatives): + # no use preserving these if we're moving derivatives around + return self.rec(expr.child, derivatives) + + def map_sum(self, expr, derivatives): + from pymbolic.primitives import flattened_sum + return flattened_sum(tuple( + self.rec(child, derivatives) for child in expr.children)) + + def map_product(self, expr, derivatives): + from grudge.optemplate.tools import is_scalar + from pytools import partition + scalars, nonscalars = partition(is_scalar, expr.children) + + if len(nonscalars) != 1: + return DerivativeJoiner()(expr) + else: + from pymbolic import flattened_product + factor = flattened_product(scalars) + nonscalar, = nonscalars + + sub_derivatives = {} + nonscalar = self.rec(nonscalar, sub_derivatives) + + def do_map(expr): + if is_scalar(expr): + return expr + else: + return self.rec(expr, derivatives) + + for operator, operands in sub_derivatives.iteritems(): + for operand in operands: + derivatives.setdefault(operator, []).append( + factor*operand) + + return factor*nonscalar + + def map_constant(self, expr, *args): + return DerivativeJoiner()(expr) + + def map_scalar_parameter(self, expr, *args): + return DerivativeJoiner()(expr) + + def map_if_positive(self, expr, *args): + return DerivativeJoiner()(expr) + + def map_power(self, expr, *args): + return DerivativeJoiner()(expr) + + # these two are necessary because they're forwarding targets + def map_algebraic_leaf(self, expr, *args): + return DerivativeJoiner()(expr) + + def map_quotient(self, expr, *args): + return DerivativeJoiner()(expr) + + map_node_coordinate_component = map_algebraic_leaf + map_normal_component = map_algebraic_leaf + map_jacobian = map_algebraic_leaf + map_inverse_metric_derivative = map_algebraic_leaf + map_forward_metric_derivative = map_algebraic_leaf + + +class DerivativeJoiner(CSECachingMapperMixin, IdentityMapper): + """Joins derivatives: + + .. math:: + + \frac{\partial A}{\partial x} + \frac{\partial B}{\partial x} + \rightarrow + \frac{\partial (A+B)}{\partial x}. + """ + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_sum(self, expr): + idj = _InnerDerivativeJoiner() + + def invoke_idj(expr): + sub_derivatives = {} + result = idj(expr, sub_derivatives) + if not sub_derivatives: + return expr + else: + for operator, operands in sub_derivatives.iteritems(): + derivatives.setdefault(operator, []).extend(operands) + + return result + + derivatives = {} + new_children = [invoke_idj(child) + for child in expr.children] + + for operator, operands in derivatives.iteritems(): + new_children.insert(0, operator( + sum(self.rec(operand) for operand in operands))) + + from pymbolic.primitives import flattened_sum + return flattened_sum(new_children) + + +class _InnerInverseMassContractor(pymbolic.mapper.RecursiveMapper): + def __init__(self, outer_mass_contractor): + self.outer_mass_contractor = outer_mass_contractor + self.extra_operator_count = 0 + + def map_constant(self, expr): + from grudge.tools import is_zero + from grudge.optemplate import InverseMassOperator, OperatorBinding + + if is_zero(expr): + return 0 + else: + return OperatorBinding( + InverseMassOperator(), + self.outer_mass_contractor(expr)) + + def map_algebraic_leaf(self, expr): + from grudge.optemplate import InverseMassOperator, OperatorBinding + + return OperatorBinding( + InverseMassOperator(), + self.outer_mass_contractor(expr)) + + def map_operator_binding(self, binding): + from grudge.optemplate import ( + MassOperator, StiffnessOperator, StiffnessTOperator, + DifferentiationOperator, + MInvSTOperator, InverseMassOperator, + FluxOperator, BoundaryFluxOperator) + + if isinstance(binding.op, MassOperator): + return binding.field + elif isinstance(binding.op, StiffnessOperator): + return DifferentiationOperator(binding.op.xyz_axis)( + self.outer_mass_contractor(binding.field)) + elif isinstance(binding.op, StiffnessTOperator): + return MInvSTOperator(binding.op.xyz_axis)( + self.outer_mass_contractor(binding.field)) + elif isinstance(binding.op, FluxOperator): + assert not binding.op.is_lift + + return FluxOperator(binding.op.flux, is_lift=True)( + self.outer_mass_contractor(binding.field)) + elif isinstance(binding.op, BoundaryFluxOperator): + assert not binding.op.is_lift + + return BoundaryFluxOperator(binding.op.flux, + binding.op.boundary_tag, is_lift=True)( + self.outer_mass_contractor(binding.field)) + else: + self.extra_operator_count += 1 + return InverseMassOperator()( + self.outer_mass_contractor(binding)) + + def map_sum(self, expr): + return expr.__class__(tuple(self.rec(child) for child in expr.children)) + + def map_product(self, expr): + from grudge.optemplate import InverseMassOperator, ScalarParameter + + def is_scalar(expr): + return isinstance(expr, (int, float, complex, ScalarParameter)) + + from pytools import len_iterable + nonscalar_count = len_iterable(ch + for ch in expr.children + if not is_scalar(ch)) + + if nonscalar_count > 1: + # too complicated, don't touch it + self.extra_operator_count += 1 + return InverseMassOperator()( + self.outer_mass_contractor(expr)) + else: + def do_map(expr): + if is_scalar(expr): + return expr + else: + return self.rec(expr) + return expr.__class__(tuple( + do_map(child) for child in expr.children)) + + +class InverseMassContractor(CSECachingMapperMixin, IdentityMapper): + # assumes all operators to be bound + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def map_boundary_pair(self, bp): + from grudge.optemplate import BoundaryPair + return BoundaryPair(self.rec(bp.field), self.rec(bp.bfield), bp.tag) + + def map_operator_binding(self, binding): + # we only care about bindings of inverse mass operators + from grudge.optemplate import InverseMassOperator + + if isinstance(binding.op, InverseMassOperator): + iimc = _InnerInverseMassContractor(self) + proposed_result = iimc(binding.field) + if iimc.extra_operator_count > 1: + # We're introducing more work than we're saving. + # Don't perform the simplification + return binding.op(self.rec(binding.field)) + else: + return proposed_result + else: + return binding.op(self.rec(binding.field)) + +# }}} + + +# {{{ error checker + +class ErrorChecker(CSECachingMapperMixin, IdentityMapper): + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def __init__(self, mesh): + self.mesh = mesh + + def map_operator_binding(self, expr): + from grudge.optemplate import DiffOperatorBase + + if isinstance(expr.op, DiffOperatorBase): + if (self.mesh is not None + and expr.op.xyz_axis >= self.mesh.dimensions): + raise ValueError("optemplate tries to differentiate along a " + "non-existent axis (e.g. Z in 2D)") + + # FIXME: Also check fluxes + return IdentityMapper.map_operator_binding(self, expr) + + def map_normal(self, expr): + if self.mesh is not None and expr.axis >= self.mesh.dimensions: + raise ValueError("optemplate tries to differentiate along a " + "non-existent axis (e.g. Z in 2D)") + + return expr + +# }}} + + +# {{{ collectors for various optemplate components + +class CollectorMixin(OperatorReducerMixin, LocalOpReducerMixin, FluxOpReducerMixin): + def combine(self, values): + from pytools import flatten + return set(flatten(values)) + + def map_constant(self, bpair): + return set() + + map_operator = map_constant + map_variable = map_constant + map_ones = map_constant + map_node_coordinate_component = map_constant + map_normal_component = map_constant + map_jacobian = map_constant + map_forward_metric_derivative = map_constant + map_inverse_metric_derivative = map_constant + map_scalar_parameter = map_constant + map_c_function = map_constant + + def map_whole_domain_flux(self, expr): + result = set() + + for ii in expr.interiors: + result.update(self.rec(ii.field_expr)) + + for bi in expr.boundaries: + result.update(self.rec(bi.bpair.field)) + result.update(self.rec(bi.bpair.bfield)) + + return result + + +class FluxCollector(CSECachingMapperMixin, CollectorMixin, CombineMapper): + map_common_subexpression_uncached = \ + CombineMapper.map_common_subexpression + + def map_operator_binding(self, expr): + from grudge.optemplate import FluxOperatorBase + + if isinstance(expr.op, FluxOperatorBase): + result = set([expr]) + else: + result = set() + + return result | CombineMapper.map_operator_binding(self, expr) + + def map_whole_domain_flux(self, wdflux): + result = set([wdflux]) + + for intr in wdflux.interiors: + result |= self.rec(intr.field_expr) + for bdry in wdflux.boundaries: + result |= self.rec(bdry.bpair) + + return result + + +class BoundaryTagCollector(CollectorMixin, CombineMapper): + def map_boundary_pair(self, bpair): + return set([bpair.tag]) + + +class GeometricFactorCollector(CollectorMixin, CombineMapper): + def map_jacobian(self, expr): + return set([expr]) + + map_forward_metric_derivative = map_jacobian + map_inverse_metric_derivative = map_jacobian + + +class BoundOperatorCollector(CSECachingMapperMixin, CollectorMixin, CombineMapper): + def __init__(self, op_class): + self.op_class = op_class + + map_common_subexpression_uncached = \ + CombineMapper.map_common_subexpression + + def map_operator_binding(self, expr): + if isinstance(expr.op, self.op_class): + result = set([expr]) + else: + result = set() + + return result | CombineMapper.map_operator_binding(self, expr) + + +class FluxExchangeCollector(CSECachingMapperMixin, CollectorMixin, CombineMapper): + map_common_subexpression_uncached = \ + CombineMapper.map_common_subexpression + + def map_flux_exchange(self, expr): + return set([expr]) + +# }}} + + +# {{{ evaluation + +class Evaluator(pymbolic.mapper.evaluator.EvaluationMapper): + def map_boundary_pair(self, bp): + from grudge.optemplate.primitives import BoundaryPair + return BoundaryPair(self.rec(bp.field), self.rec(bp.bfield), bp.tag) +# }}} + + +# {{{ boundary combiner (used by CUDA backend) + +class BoundaryCombiner(CSECachingMapperMixin, IdentityMapper): + """Combines inner fluxes and boundary fluxes into a + single, whole-domain operator of type + :class:`grudge.optemplate.operators.WholeDomainFluxOperator`. + """ + def __init__(self, mesh): + self.mesh = mesh + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def gather_one_wdflux(self, expressions): + from grudge.optemplate.primitives import OperatorBinding, BoundaryPair + from grudge.optemplate.operators import WholeDomainFluxOperator + + interiors = [] + boundaries = [] + is_lift = None + # Since None is a valid value of quadrature tags, use + # the empty list to symbolize "not known", and add to + # list once something is known. + quad_tag = [] + + rest = [] + + for ch in expressions: + from grudge.optemplate.operators import FluxOperatorBase + if (isinstance(ch, OperatorBinding) + and isinstance(ch.op, FluxOperatorBase)): + skip = False + + my_is_lift = ch.op.is_lift + + if is_lift is None: + is_lift = my_is_lift + else: + if is_lift != my_is_lift: + skip = True + + from grudge.optemplate.operators import \ + QuadratureFluxOperatorBase + + if isinstance(ch.op, QuadratureFluxOperatorBase): + my_quad_tag = ch.op.quadrature_tag + else: + my_quad_tag = None + + if quad_tag: + if quad_tag[0] != my_quad_tag: + skip = True + else: + quad_tag.append(my_quad_tag) + + if skip: + rest.append(ch) + continue + + if isinstance(ch.field, BoundaryPair): + bpair = self.rec(ch.field) + if self.mesh.tag_to_boundary.get(bpair.tag, []): + boundaries.append(WholeDomainFluxOperator.BoundaryInfo( + flux_expr=ch.op.flux, + bpair=bpair)) + else: + interiors.append(WholeDomainFluxOperator.InteriorInfo( + flux_expr=ch.op.flux, + field_expr=self.rec(ch.field))) + else: + rest.append(ch) + + if interiors or boundaries: + wdf = WholeDomainFluxOperator( + is_lift=is_lift, + interiors=interiors, + boundaries=boundaries, + quadrature_tag=quad_tag[0]) + else: + wdf = None + return wdf, rest + + def map_operator_binding(self, expr): + from grudge.optemplate.operators import FluxOperatorBase + if isinstance(expr.op, FluxOperatorBase): + wdf, rest = self.gather_one_wdflux([expr]) + assert not rest + return wdf + else: + return IdentityMapper \ + .map_operator_binding(self, expr) + + def map_sum(self, expr): + # FIXME: With flux joining now in the compiler, this is + # probably now unnecessary. + + from pymbolic.primitives import flattened_sum + + result = 0 + expressions = expr.children + while True: + wdf, expressions = self.gather_one_wdflux(expressions) + if wdf is not None: + result += wdf + else: + return result + flattened_sum(self.rec(r_i) for r_i in expressions) +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/symbolic/mappers/bc_to_flux.py b/grudge/symbolic/mappers/bc_to_flux.py new file mode 100644 index 0000000000000000000000000000000000000000..aa2fe090fca46307dad4ff2db2d58634c9dcd927 --- /dev/null +++ b/grudge/symbolic/mappers/bc_to_flux.py @@ -0,0 +1,300 @@ +"""Operator template mapper: BC-to-flux rewriting.""" + +from __future__ import division + +__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 pytools import memoize_method +from pymbolic.mapper import CSECachingMapperMixin +from grudge.optemplate.mappers import ( + IdentityMapper, DependencyMapper, CombineMapper, + OperatorReducerMixin) + + +class ExpensiveBoundaryOperatorDetector(CombineMapper): + def combine(self, values): + for val in values: + if val: + return True + + return False + + def map_operator_binding(self, expr): + from grudge.optemplate import (BoundarizeOperator, + FluxExchangeOperator, + QuadratureGridUpsampler, + QuadratureBoundaryGridUpsampler) + + if isinstance(expr.op, BoundarizeOperator): + return False + + elif isinstance(expr.op, FluxExchangeOperator): + # FIXME: Duplication of these is an even bigger problem! + return True + + elif isinstance(expr.op, ( + QuadratureGridUpsampler, + QuadratureBoundaryGridUpsampler)): + return True + + else: + raise RuntimeError("Found '%s' in a boundary term. " + "To the best of my knowledge, no grudge operator applies " + "directly to boundary data, so this is likely in error." + % expr.op) + + def map_common_subexpression(self, expr): + # If there are any expensive operators below here, this + # CSE will catch them, so we can easily flux-CSE down to + # here. + + return False + + def map_normal_component(self, expr): + return False + + map_variable = map_normal_component + map_constant = map_normal_component + + @memoize_method + def __call__(self, expr): + return CombineMapper.__call__(self, expr) + + +class BCToFluxRewriter(CSECachingMapperMixin, IdentityMapper): + """Operates on :class:`FluxOperator` instances bound to + :class:`BoundaryPair`. If the boundary pair's *bfield* is an expression of + what's available in the *field*, we can avoid fetching the data for the + explicit boundary condition and just substitute the *bfield* expression + into the flux. This mapper does exactly that. + """ + + map_common_subexpression_uncached = \ + IdentityMapper.map_common_subexpression + + def __init__(self): + self.expensive_bdry_op_detector = \ + ExpensiveBoundaryOperatorDetector() + + def map_operator_binding(self, expr): + from grudge.optemplate.operators import FluxOperatorBase + from grudge.optemplate.primitives import BoundaryPair + from grudge.flux import FluxSubstitutionMapper, FieldComponent + + if not (isinstance(expr.op, FluxOperatorBase) + and isinstance(expr.field, BoundaryPair)): + return IdentityMapper.map_operator_binding(self, expr) + + bpair = expr.field + vol_field = bpair.field + bdry_field = bpair.bfield + flux = expr.op.flux + + bdry_dependencies = DependencyMapper( + include_calls="descend_args", + include_operator_bindings=True)(bdry_field) + + vol_dependencies = DependencyMapper( + include_operator_bindings=True)(vol_field) + + vol_bdry_intersection = bdry_dependencies & vol_dependencies + if vol_bdry_intersection: + raise RuntimeError("Variables are being used as both " + "boundary and volume quantities: %s" + % ", ".join(str(v) for v in vol_bdry_intersection)) + + # Step 1: Find maximal flux-evaluable subexpression of boundary field + # in given BoundaryPair. + + class MaxBoundaryFluxEvaluableExpressionFinder( + IdentityMapper, OperatorReducerMixin): + + def __init__(self, vol_expr_list, expensive_bdry_op_detector): + self.vol_expr_list = vol_expr_list + self.vol_expr_to_idx = dict((vol_expr, idx) + for idx, vol_expr in enumerate(vol_expr_list)) + + self.bdry_expr_list = [] + self.bdry_expr_to_idx = {} + + self.expensive_bdry_op_detector = expensive_bdry_op_detector + + # {{{ expression registration + def register_boundary_expr(self, expr): + try: + return self.bdry_expr_to_idx[expr] + except KeyError: + idx = len(self.bdry_expr_to_idx) + self.bdry_expr_to_idx[expr] = idx + self.bdry_expr_list.append(expr) + return idx + + def register_volume_expr(self, expr): + try: + return self.vol_expr_to_idx[expr] + except KeyError: + idx = len(self.vol_expr_to_idx) + self.vol_expr_to_idx[expr] = idx + self.vol_expr_list.append(expr) + return idx + + # }}} + + # {{{ map_xxx routines + + @memoize_method + def map_common_subexpression(self, expr): + # Here we need to decide whether this CSE should be turned into + # a flux CSE or not. This is a good idea if the transformed + # expression only contains "bare" volume or boundary + # expressions. However, as soon as an operator is applied + # somewhere in the subexpression, the CSE should not be touched + # in order to avoid redundant evaluation of that operator. + # + # Observe that at the time of this writing (Feb 2010), the only + # operators that may occur in boundary expressions are + # quadrature-related. + + has_expensive_operators = \ + self.expensive_bdry_op_detector(expr.child) + + if has_expensive_operators: + return FieldComponent( + self.register_boundary_expr(expr), + is_interior=False) + else: + return IdentityMapper.map_common_subexpression(self, expr) + + def map_normal(self, expr): + raise RuntimeError("Your operator template contains a flux normal. " + "You may find this confusing, but you can't do that. " + "It turns out that you need to use " + "grudge.optemplate.make_normal() for normals in boundary " + "terms of operator templates.") + + def map_normal_component(self, expr): + if expr.boundary_tag != bpair.tag: + raise RuntimeError("BoundaryNormalComponent and BoundaryPair " + "do not agree about boundary tag: %s vs %s" + % (expr.boundary_tag, bpair.tag)) + + from grudge.flux import Normal + return Normal(expr.axis) + + def map_variable(self, expr): + return FieldComponent( + self.register_boundary_expr(expr), + is_interior=False) + + map_subscript = map_variable + + def map_operator_binding(self, expr): + from grudge.optemplate import (BoundarizeOperator, + FluxExchangeOperator, + QuadratureGridUpsampler, + QuadratureBoundaryGridUpsampler) + + if isinstance(expr.op, BoundarizeOperator): + if expr.op.tag != bpair.tag: + raise RuntimeError("BoundarizeOperator and BoundaryPair " + "do not agree about boundary tag: %s vs %s" + % (expr.op.tag, bpair.tag)) + + return FieldComponent( + self.register_volume_expr(expr.field), + is_interior=True) + + elif isinstance(expr.op, FluxExchangeOperator): + from grudge.mesh import TAG_RANK_BOUNDARY + op_tag = TAG_RANK_BOUNDARY(expr.op.rank) + if bpair.tag != op_tag: + raise RuntimeError("BoundarizeOperator and " + "FluxExchangeOperator do not agree about " + "boundary tag: %s vs %s" + % (op_tag, bpair.tag)) + return FieldComponent( + self.register_boundary_expr(expr), + is_interior=False) + + elif isinstance(expr.op, QuadratureBoundaryGridUpsampler): + if bpair.tag != expr.op.boundary_tag: + raise RuntimeError("BoundarizeOperator " + "and QuadratureBoundaryGridUpsampler " + "do not agree about boundary tag: %s vs %s" + % (expr.op.boundary_tag, bpair.tag)) + return FieldComponent( + self.register_boundary_expr(expr), + is_interior=False) + + elif isinstance(expr.op, QuadratureGridUpsampler): + # We're invoked before operator specialization, so we may + # see these instead of QuadratureBoundaryGridUpsampler. + return FieldComponent( + self.register_boundary_expr(expr), + is_interior=False) + + else: + raise RuntimeError("Found '%s' in a boundary term. " + "To the best of my knowledge, no grudge operator applies " + "directly to boundary data, so this is likely in error." + % expr.op) + + def map_flux_exchange(self, expr): + return FieldComponent( + self.register_boundary_expr(expr), + is_interior=False) + # }}} + + from grudge.tools import is_obj_array + if not is_obj_array(vol_field): + vol_field = [vol_field] + + mbfeef = MaxBoundaryFluxEvaluableExpressionFinder(list(vol_field), + self.expensive_bdry_op_detector) + #from grudge.optemplate.tools import pretty + #print pretty(bdry_field) + #raw_input("YO") + new_bdry_field = mbfeef(bdry_field) + + # Step II: Substitute the new_bdry_field into the flux. + def sub_bdry_into_flux(expr): + if isinstance(expr, FieldComponent) and not expr.is_interior: + if expr.index == 0 and not is_obj_array(bdry_field): + return new_bdry_field + else: + return new_bdry_field[expr.index] + else: + return None + + new_flux = FluxSubstitutionMapper(sub_bdry_into_flux)(flux) + + from grudge.tools import is_zero, make_obj_array + if is_zero(new_flux): + return 0 + else: + return type(expr.op)(new_flux, *expr.op.__getinitargs__()[1:])( + BoundaryPair( + make_obj_array([self.rec(e) for e in mbfeef.vol_expr_list]), + make_obj_array([self.rec(e) for e in mbfeef.bdry_expr_list]), + bpair.tag)) diff --git a/grudge/symbolic/mappers/type_inference.py b/grudge/symbolic/mappers/type_inference.py new file mode 100644 index 0000000000000000000000000000000000000000..a88cfe5a50ec144782189e56f4854525b7e52ebf --- /dev/null +++ b/grudge/symbolic/mappers/type_inference.py @@ -0,0 +1,731 @@ +"""Operator template type inference.""" + +from __future__ import division + +__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. +""" + +import pymbolic.mapper + + +# {{{ representation tags + +class NodalRepresentation(object): + """A tag representing nodal representation. + + Volume and boundary vectors below are represented either nodally or on a + quadrature grid. This tag expresses one of the two. + """ + def __repr__(self): + return "Nodal" + + def __eq__(self, other): + return type(self) == type(other) + + def __ne__(self, other): + return not self.__eq__(other) + + +class QuadratureRepresentation(object): + """A tag representing representation on a quadrature grid tagged with + *quadrature_tag". + + Volume and boundary vectors below are represented either nodally or on a + quadrature grid. This tag expresses one of the two. + """ + def __init__(self, quadrature_tag): + self.quadrature_tag = quadrature_tag + + def __eq__(self, other): + return (type(self) == type(other) + and self.quadrature_tag == other.quadrature_tag) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "Quadrature(%r)" % self.quadrature_tag + +# }}} + + +# {{{ type information -------------------------------------------------------- + +class type_info: + """These classes represent various bits and pieces of information that + we may deduce about expressions in our optemplate. + """ + + # serves only as a namespace, thus lower case + + # {{{ generic type info base classes + class TypeInfo(object): + def unify(self, other, expr=None): + """Return a type that can represent both *self* and *other*. + If impossible, raise :exc:`TypeError`. Subtypes should override + :meth:`unify_inner`. + """ + # shortcut + if self == other: + return self + + u_s_o = self.unify_inner(other) + u_o_s = other.unify_inner(self) + + if u_s_o is NotImplemented: + if u_o_s is NotImplemented: + if expr is not None: + from grudge.optemplate.tools import pretty + raise TypeError("types '%s' and '%s' for '%s' " + "cannot be unified" % (self, other, + pretty(expr))) + else: + raise TypeError("types '%s' and '%s' cannot be unified" + % (self, other)) + else: + return u_o_s + elif u_o_s is NotImplemented: + return u_s_o + + if u_s_o != u_o_s: + raise RuntimeError("types '%s' and '%s' don't agree about " + "their unifier" % (self, other)) + return u_s_o + + def unify_inner(self, other): + """Actual implementation that tries to unify self and other. + May return *NotImplemented* to indicate that the reverse unification + should be tried. This methods is overriden by derived classes. + Derived classes should delegate to base classes if they don't know the + answer. + """ + return NotImplemented + + def __eq__(self, other): + return (type(self) == type(other) + and self.__getinitargs__() == other.__getinitargs__()) + + def __ne__(self, other): + return not self.__eq__(other) + + class StatelessTypeInfo(TypeInfo): + def __getinitargs__(self): + return () + + class FinalType(TypeInfo): + """If a :class:`TypeInfo` instance is also an instance of this class, + no more information can be added about this type. As a result, this + type only unifies with equal instances. + """ + # }}} + + # {{{ simple types: no type, scalar + class NoType(StatelessTypeInfo): + """Represents "nothing known about type".""" + def unify_inner(self, other): + return other + + def __repr__(self): + return "NoType" + + # this singleton should be the only instance ever created of NoType + no_type = NoType() + + class Scalar(StatelessTypeInfo, FinalType): + def __repr__(self): + return "Scalar" + # }}} + + # {{{ tagged type base classes: representation, domain + class VectorRepresentationBase(object): + def __init__(self, repr_tag): + self.repr_tag = repr_tag + + def __getinitargs__(self): + return (self.repr_tag,) + + class VolumeVectorBase(object): + def __getinitargs__(self): + return () + + class InteriorFacesVectorBase(object): + def __getinitargs__(self): + return () + + class BoundaryVectorBase(object): + def __init__(self, boundary_tag): + self.boundary_tag = boundary_tag + + def __getinitargs__(self): + return (self.boundary_tag,) + # }}} + + # {{{ single-aspect-known unification helper types + class KnownVolume(TypeInfo, VolumeVectorBase): + """Type information indicating that this must be a volume vector + of unknown representation. + """ + + def __repr__(self): + return "KnownAsVolume" + + def unify_inner(self, other): + # Unification with KnownRepresentation is handled in KnownRepresentation. + # Here, we only need to unify with VolumeVector. + + if isinstance(other, type_info.VolumeVector): + return other + else: + return type_info.TypeInfo.unify_inner(self, other) + + class KnownInteriorFaces(TypeInfo, InteriorFacesVectorBase): + """Type information indicating that this must be a vector + of interior face values. + + .. note:: + + These vectors are only necessary in a quadrature setting. + """ + + def __repr__(self): + return "KnownAsIntFace" + + def unify_inner(self, other): + # Unification with KnownRepresentation is handled in KnownRepresentation. + # Here, we only need to unify with InteriorFacesVector. + + if isinstance(other, type_info.InteriorFacesVector): + return other + elif isinstance(other, type_info.KnownVolume): + return type_info.VolumeVector(NodalRepresentation()) + elif other == type_info.VolumeVector(NodalRepresentation()): + return other + else: + return type_info.TypeInfo.unify_inner(self, other) + + class KnownBoundary(TypeInfo, BoundaryVectorBase): + """Type information indicating that this must be a boundary vector.""" + + def __repr__(self): + return "KnownAsBoundary(%s)" % self.boundary_tag + + def unify_inner(self, other): + # Unification with KnownRepresentation is handled in KnownRepresentation. + # Here, we only need to unify with BoundaryVector. + + if (isinstance(other, type_info.BoundaryVector) + and self.boundary_tag == other.boundary_tag): + return other + else: + return type_info.TypeInfo.unify_inner(self, other) + + class KnownRepresentation(TypeInfo, VectorRepresentationBase): + """Type information indicating that the representation (see + representation tags, above) is known, but nothing else (e.g. whether + this is a boundary or volume vector). + """ + def __repr__(self): + return "KnownRepresentation(%s)" % self.repr_tag + + def unify_inner(self, other): + if (isinstance(other, type_info.VolumeVector) + and self.repr_tag == other.repr_tag): + return other + elif (isinstance(other, type_info.BoundaryVector) + and self.repr_tag == other.repr_tag): + return other + elif isinstance(other, type_info.KnownVolume): + return type_info.VolumeVector(self.repr_tag) + elif isinstance(other, type_info.KnownInteriorFaces): + if isinstance(self.repr_tag, NodalRepresentation): + return type_info.VolumeVector(self.repr_tag) + else: + return type_info.InteriorFacesVector(self.repr_tag) + elif isinstance(other, type_info.KnownBoundary): + return type_info.BoundaryVector(other.boundary_tag, self.repr_tag) + else: + return type_info.TypeInfo.unify_inner(self, other) + # }}} + + # {{{ fully specified grudge data types + class VolumeVector(FinalType, VectorRepresentationBase, VolumeVectorBase): + def __repr__(self): + return "Volume(%s)" % self.repr_tag + + class InteriorFacesVector(FinalType, VectorRepresentationBase, + InteriorFacesVectorBase): + def __init__(self, repr_tag): + if not isinstance(repr_tag, QuadratureRepresentation): + raise TypeError("InteriorFacesVector is not usable with non-" + "quadrature representations") + type_info.VectorRepresentationBase.__init__(self, repr_tag) + + def __repr__(self): + return "InteriorFaces(%s)" % self.repr_tag + + class BoundaryVector(FinalType, BoundaryVectorBase, + VectorRepresentationBase): + def __init__(self, boundary_tag, repr_tag): + type_info.BoundaryVectorBase.__init__(self, boundary_tag) + type_info.VectorRepresentationBase.__init__(self, repr_tag) + + def __repr__(self): + return "Boundary(%s, %s)" % (self.boundary_tag, self.repr_tag) + + def __getinitargs__(self): + return (self.boundary_tag, self.repr_tag) + # }}} + + +# {{{ aspect extraction functions + +def extract_representation(ti): + try: + own_repr_tag = ti.repr_tag + except AttributeError: + return type_info.no_type + else: + return type_info.KnownRepresentation(own_repr_tag) + + +def extract_domain(ti): + if isinstance(ti, type_info.VolumeVectorBase): + return type_info.KnownVolume() + elif isinstance(ti, type_info.BoundaryVectorBase): + return type_info.KnownBoundary(ti.boundary_tag) + else: + return type_info.no_type + +# }}} + +# }}} + + +# {{{ TypeDict helper type + +class TypeDict(object): + def __init__(self, hints): + self.container = hints.copy() + self.change_flag = False + + def __getitem__(self, expr): + try: + return self.container[expr] + except KeyError: + return type_info.no_type + + def __setitem__(self, expr, new_tp): + if new_tp is type_info.no_type: + return + + try: + old_tp = self.container[expr] + except KeyError: + self.container[expr] = new_tp + self.change_flag = True + else: + tp = old_tp.unify(new_tp, expr) + if tp != old_tp: + self.change_flag = True + self.container[expr] = tp + + def iteritems(self): + return self.container.iteritems() + +# }}} + + +# {{{ type inference mapper + +class TypeInferrer(pymbolic.mapper.RecursiveMapper): + def __init__(self): + self.cse_last_results = {} + + def __call__(self, expr, type_hints={}): + typedict = TypeDict(type_hints) + + while True: + typedict.change_flag = False + + def infer_for_expr(expr): + tp = pymbolic.mapper.RecursiveMapper.__call__(self, expr, typedict) + typedict[expr] = tp + + # Numpy arrays occur either at the top level or in flux + # expressions. This code handles the top level case. + from pytools.obj_array import with_object_array_or_scalar + with_object_array_or_scalar(infer_for_expr, expr) + + if not typedict.change_flag: + # nothing has changed any more, type information has 'converged' + break + + # check that type inference completed successfully + for expr, tp in typedict.iteritems(): + if not isinstance(tp, type_info.FinalType): + raise RuntimeError("type inference was unable to deduce " + "complete type information for '%s' (only '%s')" + % (expr, tp)) + + return typedict + + def rec(self, expr, typedict): + tp = pymbolic.mapper.RecursiveMapper.rec(self, expr, typedict) + typedict[expr] = tp + return tp + + # Information needs to propagate upward (toward the leaves) *and* + # downward (toward the roots) in the expression tree. + + # {{{ base cases + def infer_for_children(self, expr, typedict, children): + # This routine allows scalars among children and treats them as + # not type-changing + + tp = typedict[expr] + + non_scalar_exprs = [] + + for child in children: + if tp is type_info.no_type: + tp = self.rec(child, typedict) + if isinstance(tp, type_info.Scalar): + tp = type_info.no_type + else: + non_scalar_exprs.append(child) + else: + other_tp = self.rec(child, typedict) + + if not isinstance(other_tp, type_info.Scalar): + non_scalar_exprs.append(child) + tp = tp.unify(other_tp, child) + + for child in non_scalar_exprs: + typedict[child] = tp + + if not non_scalar_exprs: + tp = type_info.Scalar() + + return tp + + # }}} + + def map_sum(self, expr, typedict): + return self.infer_for_children(expr, typedict, expr.children) + + def map_product(self, expr, typedict): + return self.infer_for_children(expr, typedict, expr.children) + + def map_quotient(self, expr, typedict): + return self.infer_for_children(expr, typedict, + children=[expr.numerator, expr.denominator]) + + def map_power(self, expr, typedict): + return self.infer_for_children(expr, typedict, + children=[expr.base, expr.exponent]) + + def map_if(self, expr, typedict): + return self.infer_for_children(expr, typedict, + children=[expr.condition, expr.then, expr.else_]) + + def map_comparison(self, expr, typedict): + return self.infer_for_children(expr, typedict, + children=[expr.left, expr.right]) + + def map_if_positive(self, expr, typedict): + return self.infer_for_children(expr, typedict, + children=[expr.criterion, expr.then, expr.else_]) + + def map_call(self, expr, typedict): + # assumes functions to be non-type-changing + return self.infer_for_children(expr, typedict, + children=expr.parameters) + + def map_operator_binding(self, expr, typedict): + from grudge.optemplate.operators import ( + NodalReductionOperator, + + DiffOperatorBase, + ReferenceDiffOperatorBase, + + MassOperatorBase, + ReferenceMassOperatorBase, + + ElementwiseMaxOperator, + BoundarizeOperator, FluxExchangeOperator, + FluxOperatorBase, + QuadratureGridUpsampler, QuadratureBoundaryGridUpsampler, + QuadratureInteriorFacesGridUpsampler, + + MassOperator, + ReferenceMassOperator, + ReferenceQuadratureMassOperator, + + StiffnessTOperator, + ReferenceStiffnessTOperator, + ReferenceQuadratureStiffnessTOperator, + + ElementwiseLinearOperator) + + if isinstance(expr.op, NodalReductionOperator): + typedict[expr.field] = type_info.KnownVolume() + self.rec(expr.field, typedict) + return type_info.Scalar() + + elif isinstance(expr.op, + (ReferenceQuadratureStiffnessTOperator, + ReferenceQuadratureMassOperator)): + typedict[expr.field] = type_info.VolumeVector( + QuadratureRepresentation(expr.op.quadrature_tag)) + self.rec(expr.field, typedict) + return type_info.VolumeVector(NodalRepresentation()) + + elif isinstance(expr.op, + (ReferenceStiffnessTOperator, StiffnessTOperator)): + # stiffness_T can be specialized for quadrature by OperatorSpecializer + typedict[expr.field] = type_info.KnownVolume() + self.rec(expr.field, typedict) + return type_info.VolumeVector(NodalRepresentation()) + + elif isinstance(expr.op, + (ReferenceMassOperator, MassOperator)): + # mass can be specialized for quadrature by OperatorSpecializer + typedict[expr.field] = type_info.KnownVolume() + self.rec(expr.field, typedict) + return type_info.VolumeVector(NodalRepresentation()) + + elif isinstance(expr.op, ( + DiffOperatorBase, + ReferenceDiffOperatorBase, + MassOperatorBase, + ReferenceMassOperatorBase)): + # all other operators are purely nodal + typedict[expr.field] = type_info.VolumeVector(NodalRepresentation()) + self.rec(expr.field, typedict) + return type_info.VolumeVector(NodalRepresentation()) + + elif isinstance(expr.op, ElementwiseMaxOperator): + typedict[expr.field] = typedict[expr].unify( + type_info.KnownVolume(), expr.field) + return self.rec(expr.field, typedict) + + elif isinstance(expr.op, BoundarizeOperator): + # upward propagation: argument has same rep tag as result + typedict[expr.field] = type_info.KnownVolume().unify( + extract_representation(type_info), expr.field) + + self.rec(expr.field, typedict) + + # downward propagation: result has same rep tag as argument + return type_info.KnownBoundary(expr.op.tag).unify( + extract_representation(typedict[expr.field]), expr) + + elif isinstance(expr.op, FluxExchangeOperator): + raise NotImplementedError + + elif isinstance(expr.op, FluxOperatorBase): + from pytools.obj_array import with_object_array_or_scalar + from grudge.optemplate.primitives import BoundaryPair + + repr_tag_cell = [type_info.no_type] + + def process_vol_flux_arg(flux_arg): + typedict[flux_arg] = type_info.KnownInteriorFaces() \ + .unify(repr_tag_cell[0], flux_arg) + repr_tag_cell[0] = extract_representation( + self.rec(flux_arg, typedict)) + + if isinstance(expr.field, BoundaryPair): + def process_bdry_flux_arg(flux_arg): + typedict[flux_arg] = type_info.KnownBoundary(bpair.tag) \ + .unify(repr_tag_cell[0], flux_arg) + + repr_tag_cell[0] = extract_representation( + self.rec(flux_arg, typedict)) + + bpair = expr.field + with_object_array_or_scalar(process_vol_flux_arg, bpair.field) + with_object_array_or_scalar(process_bdry_flux_arg, bpair.bfield) + else: + with_object_array_or_scalar(process_vol_flux_arg, expr.field) + + return type_info.VolumeVector(NodalRepresentation()) + + elif isinstance(expr.op, QuadratureGridUpsampler): + typedict[expr.field] = extract_domain(typedict[expr]) + self.rec(expr.field, typedict) + return type_info.KnownRepresentation( + QuadratureRepresentation(expr.op.quadrature_tag))\ + .unify(extract_domain(typedict[expr.field]), expr) + + elif isinstance(expr.op, QuadratureInteriorFacesGridUpsampler): + typedict[expr.field] = type_info.VolumeVector( + NodalRepresentation()) + self.rec(expr.field, typedict) + return type_info.InteriorFacesVector( + QuadratureRepresentation(expr.op.quadrature_tag)) + + elif isinstance(expr.op, QuadratureBoundaryGridUpsampler): + typedict[expr.field] = type_info.BoundaryVector( + expr.op.boundary_tag, NodalRepresentation()) + self.rec(expr.field, typedict) + return type_info.BoundaryVector( + expr.op.boundary_tag, + QuadratureRepresentation(expr.op.quadrature_tag)) + + elif isinstance(expr.op, ElementwiseLinearOperator): + typedict[expr.field] = type_info.VolumeVector(NodalRepresentation()) + self.rec(expr.field, typedict) + return type_info.VolumeVector(NodalRepresentation()) + + else: + raise RuntimeError("TypeInferrer doesn't know how to handle '%s'" + % expr.op) + + def map_whole_domain_flux(self, expr, typedict): + repr_tag_cell = [type_info.no_type] + + def process_vol_flux_arg(flux_arg): + typedict[flux_arg] = type_info.KnownInteriorFaces() \ + .unify(repr_tag_cell[0], flux_arg) + repr_tag_cell[0] = extract_representation( + self.rec(flux_arg, typedict)) + + def process_bdry_flux_arg(flux_arg): + typedict[flux_arg] = type_info.KnownBoundary(bpair.tag) \ + .unify(repr_tag_cell[0], flux_arg) + + repr_tag_cell[0] = extract_representation( + self.rec(flux_arg, typedict)) + + from pytools.obj_array import with_object_array_or_scalar + for int_flux_info in expr.interiors: + with_object_array_or_scalar(process_vol_flux_arg, + int_flux_info.field_expr) + + for bdry_flux_info in expr.boundaries: + bpair = bdry_flux_info.bpair + with_object_array_or_scalar(process_vol_flux_arg, bpair.field) + with_object_array_or_scalar(process_bdry_flux_arg, bpair.bfield) + + return type_info.VolumeVector(NodalRepresentation()) + + def map_flux_exchange(self, expr, typedict): + for arg in expr.arg_fields: + typedict[arg] = type_info.VolumeVector(NodalRepresentation()) + + from grudge.mesh import TAG_RANK_BOUNDARY + return type_info.BoundaryVector( + TAG_RANK_BOUNDARY(expr.rank), + NodalRepresentation()) + + def map_constant(self, expr, typedict): + return type_info.Scalar().unify(typedict[expr], expr) + + def map_variable(self, expr, typedict): + # user-facing variables are nodal + return type_info.KnownRepresentation(NodalRepresentation())\ + .unify(typedict[expr], expr) + + map_subscript = map_variable + + def map_scalar_parameter(self, expr, typedict): + return type_info.Scalar().unify(typedict[expr], expr) + + def map_ones(self, expr, typedict): + # FIXME: This is a bit dumb. If the quadrature_tag is None, + # we don't know whether the expression was specialized + # to 'no quadrature' or if it simply does not know yet + # whether it will be on a quadrature grid. + if expr.quadrature_tag is not None: + return (type_info.VolumeVector( + QuadratureRepresentation(expr.quadrature_tag)) + .unify(typedict[expr], expr)) + else: + return (type_info.VolumeVector(NodalRepresentation()) + .unify(typedict[expr], expr)) + + def map_node_coordinate_component(self, expr, typedict): + # FIXME: This is a bit dumb. If the quadrature_tag is None, + # we don't know whether the expression was specialized + # to 'no quadrature' or if it simply does not know yet + # whether it will be on a quadrature grid. + if expr.quadrature_tag is not None: + return (type_info.VolumeVector( + QuadratureRepresentation(expr.quadrature_tag)) + .unify(typedict[expr], expr)) + else: + return (type_info.VolumeVector(NodalRepresentation()) + .unify(typedict[expr], expr)) + + def map_normal_component(self, expr, typedict): + # FIXME: This is a bit dumb. If the quadrature_tag is None, + # we don't know whether the expression was specialized + # to 'no quadrature' or if it simply does not know yet + # whether it will be on a quadrature grid. + + if expr.quadrature_tag is not None: + return (type_info.BoundaryVector(expr.boundary_tag, + QuadratureRepresentation(expr.quadrature_tag)) + .unify(typedict[expr], expr)) + else: + return (type_info.KnownBoundary(expr.boundary_tag) + .unify(typedict[expr], expr)) + + def map_jacobian(self, expr, typedict): + return type_info.KnownVolume() + + map_forward_metric_derivative = map_jacobian + map_inverse_metric_derivative = map_jacobian + + def map_common_subexpression(self, expr, typedict): + outer_tp = typedict[expr] + + last_tp = self.cse_last_results.get(expr, type_info.no_type) + if outer_tp != last_tp or last_tp == type_info.no_type: + # re-run inner type inference with new outer information + typedict[expr.child] = outer_tp + new_tp = self.rec(expr.child, typedict) + + # For correct caching, we need to make sure that + # information below this level has fully propagated. + while True: + typedict.change_flag = False + new_tp = self.rec(expr.child, typedict) + + if not typedict.change_flag: + # nothing has changed any more + break + + self.cse_last_results[expr] = new_tp + typedict[expr.child] = new_tp + + # we can be sure we *have* changed something + typedict.change_flag = True + return new_tp + else: + return last_tp + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py index e09b707618b93ec4c2d4b4c3e0a84f27ef5ad774..2cfdba75f09e07d92c12f7c8f737fa7ad92521cc 100644 --- a/grudge/symbolic/primitives.py +++ b/grudge/symbolic/primitives.py @@ -1,8 +1,6 @@ """Operator template language: primitives.""" -from __future__ import division -from __future__ import absolute_import -from six.moves import range +from __future__ import division, absolute_import __copyright__ = "Copyright (C) 2008 Andreas Kloeckner" @@ -26,10 +24,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from six.moves import range import numpy import pymbolic.primitives -import grudge.mesh +from meshmode.mesh import BTAG_ALL from pymbolic.primitives import ( # noqa make_common_subexpression, If, Comparison)