diff --git a/grudge/execution.py b/grudge/execution.py index e3646b7361d8b1606a1a234fbe6089a51127f8b7..aa60715dd5f7ecd36b3380654bc9654afdb63930 100644 --- a/grudge/execution.py +++ b/grudge/execution.py @@ -333,11 +333,11 @@ class ExecutionMapper(mappers.Evaluator, # {{{ code execution functions - def exec_assign(self, insn): + def map_insn_assign(self, insn): return [(name, self.rec(expr)) for name, expr in zip(insn.names, insn.exprs)], [] - def exec_assign_to_discr_scoped(self, insn): + def map_insn_assign_to_discr_scoped(self, insn): assignments = [] for name, expr in zip(insn.names, insn.exprs): value = self.rec(expr) @@ -346,11 +346,11 @@ class ExecutionMapper(mappers.Evaluator, return assignments, [] - def exec_assign_from_discr_scoped(self, insn): + def map_insn_assign_from_discr_scoped(self, insn): return [(insn.name, self.discr._discr_scoped_subexpr_name_to_value[insn.name])], [] - def exec_diff_batch_assign(self, insn): + def map_insn_diff_batch_assign(self, insn): field = self.rec(insn.field) repr_op = insn.operators[0] # FIXME: There's no real reason why differentiation is special, diff --git a/grudge/symbolic/compiler.py b/grudge/symbolic/compiler.py index fc4ad5f016c522c4c362190dca4ae76aedd5cbd5..7259e4e42e4959472265a6097e9c49cf09a8ba79 100644 --- a/grudge/symbolic/compiler.py +++ b/grudge/symbolic/compiler.py @@ -31,6 +31,7 @@ from pytools import Record, memoize_method, memoize from grudge import sym import grudge.symbolic.mappers as mappers from pymbolic.primitives import Variable, Subscript +from sys import intern # {{{ instructions @@ -38,6 +39,7 @@ from pymbolic.primitives import Variable, Subscript class Instruction(Record): __slots__ = [] priority = 0 + neglect_for_dofdesc_inference = False def get_assignees(self): raise NotImplementedError("no get_assignees in %s" % self.__class__) @@ -48,9 +50,6 @@ class Instruction(Record): def __str__(self): raise NotImplementedError - def get_execution_method(self, exec_mapper): - raise NotImplementedError - def __hash__(self): return id(self) @@ -69,6 +68,27 @@ def _make_dep_mapper(include_subscripts): include_calls="descend_args") +# {{{ loopy kernel instruction + +class LoopyKernelDescriptor(object): + def __init__(self, loopy_kernel, input_mappings, output_mappings, + fixed_arguments): + self.loopy_kernel = loopy_kernel + self.input_mappings = input_mappings + self.output_mappings = output_mappings + self.fixed_arguments = fixed_arguments + + +class LoopyKernelInstruction(Instruction): + comment = "" + scope_indicator = "" + + def __init__(self, per_group_kernel_descriptors): + self.per_group_kernel_descriptors = per_group_kernel_descriptors + +# }}} + + class AssignBase(Instruction): comment = "" scope_indicator = "" @@ -143,19 +163,18 @@ class Assign(AssignBase): return deps - def get_execution_method(self, exec_mapper): - return exec_mapper.exec_assign + mapper_method = intern("map_insn_assign") class ToDiscretizationScopedAssign(Assign): scope_indicator = "(to discr)-" - def get_execution_method(self, exec_mapper): - return exec_mapper.exec_assign_to_discr_scoped + mapper_method = intern("map_insn_assign_to_discr_scoped") class FromDiscretizationScopedAssign(AssignBase): scope_indicator = "(discr)-" + neglect_for_dofdesc_inference = True def __init__(self, name, **kwargs): super(FromDiscretizationScopedAssign, self).__init__(name=name, **kwargs) @@ -173,8 +192,7 @@ class FromDiscretizationScopedAssign(AssignBase): def __str__(self): return "%s <-(from discr)" % self.name - def get_execution_method(self, exec_mapper): - return exec_mapper.exec_assign_from_discr_scoped + mapper_method = intern("map_insn_assign_from_discr_scoped") class DiffBatchAssign(Instruction): @@ -212,57 +230,7 @@ class DiffBatchAssign(Instruction): return "\n".join(lines) - def get_execution_method(self, exec_mapper): - return exec_mapper.exec_diff_batch_assign - - -class FluxExchangeBatchAssign(Instruction): - """ - .. attribute:: names - .. attribute:: indices_and_ranks - .. attribute:: rank_to_index_and_name - .. attribute:: arg_fields - """ - - priority = 1 - - def __init__(self, names, indices_and_ranks, arg_fields): - rank_to_index_and_name = {} - for name, (index, rank) in zip( - names, indices_and_ranks): - rank_to_index_and_name.setdefault(rank, []).append( - (index, name)) - - Instruction.__init__(self, - names=names, - indices_and_ranks=indices_and_ranks, - rank_to_index_and_name=rank_to_index_and_name, - arg_fields=arg_fields) - - def get_assignees(self): - return set(self.names) - - @memoize_method - def get_dependencies(self): - dep_mapper = _make_dep_mapper() - result = set() - for fld in self.arg_fields: - result |= dep_mapper(fld) - return result - - def __str__(self): - lines = [] - - lines.append("{") - for n, (index, rank) in zip(self.names, self.indices_and_ranks): - lines.append(" %s <- receive index %s from rank %d [%s]" % ( - n, index, rank, self.arg_fields)) - lines.append("}") - - return "\n".join(lines) - - def get_execution_method(self, exec_mapper): - return exec_mapper.exec_flux_exchange_batch_assign + mapper_method = intern("map_insn_diff_batch_assign") # }}} @@ -484,8 +452,8 @@ class Code(object): del context[name] done_insns.add(insn) - assignments, new_futures = \ - insn.get_execution_method(exec_mapper)(insn) + mapper_method = getattr(exec_mapper, insn.mapper_method) + assignments, new_futures = mapper_method(insn) if insn is not None: for target, value in assignments: @@ -636,6 +604,9 @@ class OperatorCompiler(mappers.IdentityMapper): # Finally, walk the expression and build the code. result = super(OperatorCompiler, self).__call__(expr, codegen_state) + from grudge.symbolic.dofdesc_inference import DOFDescInferenceMapper + inf_mapper = DOFDescInferenceMapper(self.discr_code + self.eval_code) + from pytools.obj_array import make_obj_array return ( Code(self.discr_code, @@ -643,9 +614,7 @@ class OperatorCompiler(mappers.IdentityMapper): [Variable(name) for name in self.discr_scope_names_copied_to_eval])), Code( - # FIXME: Enable - #self.aggregate_assignments(self.eval_code, result), - self.eval_code, + self.aggregate_assignments(inf_mapper, self.eval_code, result), result)) # }}} @@ -799,7 +768,7 @@ class OperatorCompiler(mappers.IdentityMapper): # {{{ assignment aggregration pass - def aggregate_assignments(self, instructions, result): + def aggregate_assignments(self, inf_mapper, instructions, result): from pymbolic.primitives import Variable # {{{ aggregation helpers @@ -853,9 +822,16 @@ class OperatorCompiler(mappers.IdentityMapper): for assignee in insn.get_assignees()) from pytools import partition + from grudge.symbolic.primitives import DTAG_SCALAR + unprocessed_assigns, other_insns = partition( - # FIXME: Re-add check for scalar result, exclude - lambda insn: isinstance(insn, Assign), + lambda insn: ( + isinstance(insn, Assign) + and not isinstance(insn, ToDiscretizationScopedAssign) + and not isinstance(insn, FromDiscretizationScopedAssign) + and not any( + inf_mapper.infer_for_name(n).domain_tag == DTAG_SCALAR + for n in insn.names)), instructions) # filter out zero-flop-count assigns--no need to bother with those @@ -864,7 +840,6 @@ class OperatorCompiler(mappers.IdentityMapper): unprocessed_assigns) # filter out zero assignments - from pytools import any from grudge.tools import is_zero i = 0 @@ -872,7 +847,7 @@ class OperatorCompiler(mappers.IdentityMapper): while i < len(unprocessed_assigns): my_assign = unprocessed_assigns[i] if any(is_zero(expr) for expr in my_assign.exprs): - processed_assigns.append(unprocessed_assigns.pop()) + processed_assigns.append(unprocessed_assigns.pop(i)) else: i += 1 diff --git a/grudge/symbolic/dofdesc_inference.py b/grudge/symbolic/dofdesc_inference.py new file mode 100644 index 0000000000000000000000000000000000000000..6b8cbebb75f0d4ee0500553d955c8ad40f072376 --- /dev/null +++ b/grudge/symbolic/dofdesc_inference.py @@ -0,0 +1,218 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2017 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. +""" + + +# This is purely leaves-to-roots. No need to propagate information in the +# opposite direction. + + +from pymbolic.mapper import RecursiveMapper, CSECachingMapperMixin +from grudge.symbolic.primitives import DOFDesc, DTAG_SCALAR + + +def unify_dofdescs(dd_a, dd_b, expr=None): + if dd_a is None: + assert dd_b is not None + return dd_b + + if expr is not None: + loc_str = "in expression %s" % str(expr) + else: + loc_str = "" + + from grudge.symbolic.primitives import DTAG_SCALAR + if dd_a.domain_tag != dd_b.domain_tag: + if dd_a.domain_tag == DTAG_SCALAR: + return dd_b + elif dd_b.domain_tag == DTAG_SCALAR: + return dd_a + else: + raise ValueError("mismatched domain tags" + loc_str) + + # domain tags match + if dd_a.quadrature_tag != dd_b.quadrature_tag: + raise ValueError("mismatched quadrature tags" + loc_str) + + return dd_a + + +class InferrableMultiAssignment(object): + """An assignemnt 'instruction' which may be used as part of type + inference. + + .. method:: get_assignees(rec) + + :returns: a :class:`set` of names which are assigned values by + this assignment. + + .. method:: infer_dofdescs(rec) + + :returns: a list of ``(name, :class:`grudge.symbolic.primitives.DOFDesc`)`` + tuples, each indicating the value type of the value with *name*. + """ + + # (not a base class--only documents the interface) + + +class DOFDescInferenceMapper(RecursiveMapper, CSECachingMapperMixin): + def __init__(self, assignments, name_to_dofdesc=None, check=True): + """ + :arg assignments: a list of objects adhering to + :class:`InferrableMultiAssignment`. + :returns: an instance of :class:`DOFDescInferenceMapper` + """ + + self.check = check + + self.name_to_assignment = dict( + (name, a) + for a in assignments + if not a.neglect_for_dofdesc_inference + for name in a.get_assignees()) + + if name_to_dofdesc is None: + name_to_dofdesc = {} + else: + name_to_dofdesc = name_to_dofdesc.copy() + + self.name_to_dofdesc = name_to_dofdesc + + def infer_for_name(self, name): + try: + return self.name_to_dofdesc[name] + except KeyError: + a = self.name_to_assignment[name] + + inf_method = getattr(self, a.mapper_method) + for r_name, r_dofdesc in inf_method(a): + assert r_name not in self.name_to_dofdesc + self.name_to_dofdesc[r_name] = r_dofdesc + + return self.name_to_dofdesc[name] + + # {{{ expression mappings + + def map_constant(self, expr): + return DOFDesc(DTAG_SCALAR) + + def map_grudge_variable(self, expr): + return expr.dd + + def map_variable(self, expr): + return self.infer_for_name(expr.name) + + def map_subscript(self, expr): + # FIXME: Subscript has same type as aggregate--a bit weird + return self.rec(expr.aggregate) + + def map_arithmetic(self, expr, children): + dofdesc = None + + for ch in children: + dofdesc = unify_dofdescs(dofdesc, self.rec(ch), expr) + + if dofdesc is None: + raise ValueError("no DOFDesc found for expression %s" % expr) + else: + return dofdesc + + def map_sum(self, expr): + return self.map_arithmetic(expr, expr.children) + + map_product = map_sum + + def map_quotient(self, expr): + return self.map_arithmetic(expr, (expr.numerator, expr.denominator)) + + def map_power(self, expr): + return self.map_arithmetic(expr, (expr.base, expr.exponent)) + + def map_nodal_sum(self, expr, enclosing_prec): + return DOFDesc(DTAG_SCALAR) + + map_nodal_max = map_nodal_sum + map_nodal_min = map_nodal_sum + + def map_operator_binding(self, expr): + operator = expr.op + + if self.check: + op_dd = self.rec(expr.field) + if op_dd != operator.dd_in: + raise ValueError("mismatched input to %s " + "(got: %s, expected: %s)" + " in '%s'" + % ( + type(expr).__name__, + op_dd, expr.dd_in, + str(expr))) + + return operator.dd_out + + def map_ones(self, expr): + return expr.dd + + map_node_coordinate_component = map_ones + + def map_call(self, expr): + arg_dds = [ + self.rec(par) + for par in expr.parameters] + + assert arg_dds + + # FIXME + return arg_dds[0] + + # }}} + + # {{{ instruction mappings + + def map_insn_assign(self, insn): + return [ + (name, self.rec(expr)) + for name, expr in zip(insn.names, insn.exprs) + ] + + map_insn_assign_to_discr_scoped = map_insn_assign + + def map_insn_diff_batch_assign(self, insn): + if self.check: + repr_op = insn.operators[0] + input_dd = self.rec(insn.field) + if input_dd != repr_op.dd_in: + raise ValueError("mismatched input to %s " + "(got: %s, expected: %s)" + % ( + type(insn).__name__, + input_dd, repr_op.dd_in, + )) + + return [ + (name, op.dd_out) + for name, op in zip(insn.names, insn.operators)] + + # }}} + +# vim: foldmethod=marker diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py index 0db0a8908a2b9647c87e15046480de8d3904fbe5..b1cf4a54b90671a4c073eba4c3c7547b2a38b05a 100644 --- a/grudge/symbolic/primitives.py +++ b/grudge/symbolic/primitives.py @@ -191,6 +191,9 @@ class DOFDesc(object): if domain_tag is DTAG_SCALAR and quadrature_tag is not None: raise ValueError("cannot have nontrivial quadrature tag on scalar") + if quadrature_tag is None: + quadrature_tag = QTAG_NONE + self.domain_tag = domain_tag self.quadrature_tag = quadrature_tag