1 """Building blocks and mappers for operator expression trees."""
2
3 from __future__ import division
4
5 __copyright__ = "Copyright (C) 2008 Andreas Kloeckner"
6
7 __license__ = """
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see U{http://www.gnu.org/licenses/}.
20 """
21
22
23
24
25 import numpy
26 import pymbolic.primitives
27 import pymbolic.mapper.stringifier
28 import pymbolic.mapper.evaluator
29 import pymbolic.mapper.dependency
30 import pymbolic.mapper.substitutor
31 import pymbolic.mapper.constant_folder
32 import pymbolic.mapper.flop_counter
33 import hedge.mesh
34 from pymbolic.mapper import CSECachingMapperMixin
40 from hedge.tools import with_object_array_or_scalar
41 from pymbolic.primitives import CommonSubexpression
42 return with_object_array_or_scalar(CommonSubexpression, fields)
43
44
45
46
47 Field = pymbolic.primitives.Variable
50 if not isinstance(var_or_string, pymbolic.primitives.Expression):
51 return Field(var_or_string)
52 else:
53 return var_or_string
54
64
70 self.tag = tag
71 self.axis = axis
72
75
77 return hash((self.__class__, self.tag, self.axis))
78
80 return (other.__class__ == self.__class__
81 and other.tag == self.tag
82 and other.axis == self.axis)
83
85 return mapper.map_normal_component
86
93
99 """When the optemplate-to-code transformation is performed,
100 prioritized subexpressions work like common subexpression in
101 that they are assigned their own separate identifier/register
102 location. In addition to this behavior, prioritized subexpressions
103 are evaluated with a settable priority, allowing the user to
104 expedite or delay the evaluation of the subexpression.
105 """
106
110
113
116
117
118
119
120
121 -class Operator(pymbolic.primitives.Leaf):
124
126
127 raise RuntimeError, "symbolic operators are not callable"
128
129 - def apply(self, discr, field):
131
138
140 return hash(self.__class__)
141
143 return other.__class__ == self.__class__
144
158
161
163 from hedge.tools import field_equal
164 return (other.__class__ == self.__class__
165 and other.op == self.op
166 and field_equal(other.field, self.field))
167
169 from hedge.tools import hashable_field
170 return hash((self.__class__, self.op, hashable_field(self.field)))
171
181
183 return (self.xyz_axis,)
184
186 return hash((self.__class__, self.xyz_axis))
187
189 return (other.__class__ == self.__class__
190 and other.xyz_axis == self.xyz_axis)
191
196
197 @staticmethod
199 return element_group.diff_coefficients
200
202 return mapper.map_diff
203
205 @staticmethod
207 return element_group.minv_st
208
209 @staticmethod
211 return element_group.diff_coefficients
212
214 return mapper.map_minv_st
215
217 @staticmethod
219 return element_group.stiffness_matrices
220
221 @staticmethod
223 return element_group.stiffness_coefficients
224
226 return mapper.map_stiffness
227
229 @staticmethod
231 return element_group.stiffness_t_matrices
232
233 @staticmethod
235 return element_group.stiffness_coefficients
236
238 return mapper.map_stiffness_t
239
245 from hedge.tools import join_fields
246 return join_fields(*els)
247
254
262
263 @staticmethod
265 return element_group.jacobians
266
268 return mapper.map_mass
269
274
275 @staticmethod
277 return element_group.inverse_jacobians
278
280 return mapper.map_inverse_mass
281
290
297
299 return hash((self.__class__, self.tag))
300
302 return (other.__class__ == self.__class__
303 and other.tag == self.tag)
304
306 return mapper.map_boundarize
307
310
315 """An operator that results in the sending and receiving of
316 boundary information for its argument fields.
317 """
318
320 self.index = idx
321 self.rank = rank
322
324 return (self.index, self.rank)
325
327 return hash((self.__class__, self.index, self.rank))
328
330 return (other.__class__ == self.__class__
331 and other.index == self.index
332 and other.rank == self.rank)
333
335 return mapper.map_flux_exchange
336
337
338
339
340
341 -class BoundaryPair(pymbolic.primitives.AlgebraicLeaf):
342 """Represents a pairing of a volume and a boundary field, used for the
343 application of boundary fluxes.
344 """
345
347 self.field = field
348 self.bfield = bfield
349 self.tag = tag
350
353
356
358 return (self.field, self.bfield, self.tag)
359
361 from hedge.tools import hashable_field
362
363 return hash((self.__class__,
364 hashable_field(self.field),
365 hashable_field(self.bfield),
366 self.tag))
367
369 from hedge.tools import field_equal
370 return (self.__class__ == other.__class__
371 and field_equal(other.field, self.field)
372 and field_equal(other.bfield, self.bfield)
373 and other.tag == self.tag)
374
384
393
396
398 return hash((self.__class__, self.flux))
399
401 return (self.__class__ == other.__class__
402 and self.flux == other.flux)
403
410
417
423
437
444 if isinstance(components, int):
445 components = range(components)
446
447 from hedge.tools import join_fields
448 vfld = pymbolic.primitives.Variable(name)
449 return join_fields(*[vfld[i] for i in components])
450
455 """Return a flux operator that can be multiplied with
456 a volume field to obtain the lifted interior fluxes
457 or with a boundary pair to obtain the lifted boundary
458 flux.
459 """
460 from hedge.tools import is_obj_array
461
462 if is_obj_array(flux):
463 return VectorFluxOperator(flux)
464 else:
465 return FluxOperator(flux)
466
474
479
484
489
495 """Reduces calls to mapper methods for all local differentiation
496 operators to a single mapper method, and likewise for mass
497 operators.
498 """
499 - def map_diff(self, expr, *args, **kwargs):
501
504
507
510
511 - def map_mass(self, expr, *args, **kwargs):
513
516
521 """Reduces calls to mapper methods for all flux
522 operators to a smaller number of mapper methods.
523 """
524 - def map_flux(self, expr, *args, **kwargs):
526
527 - def map_lift(self, expr, *args, **kwargs):
529
543
553
554
555
556
557 -class CombineMapper(CombineMapperMixin, pymbolic.mapper.CombineMapper):
559
565 assert not isinstance(self, BoundOpMapperMixin), \
566 "IdentityMapper instances cannot be combined with " \
567 "the BoundOpMapperMixin"
568
569 return expr.__class__(
570 self.rec(expr.op, *args, **kwargs),
571 self.rec(expr.field, *args, **kwargs))
572
574 assert not isinstance(self, BoundOpMapperMixin), \
575 "IdentityMapper instances cannot be combined with " \
576 "the BoundOpMapperMixin"
577
578 return expr.__class__(
579 self.rec(expr.field, *args, **kwargs),
580 self.rec(expr.bfield, *args, **kwargs),
581 expr.tag)
582
584 assert not isinstance(self, BoundOpMapperMixin), \
585 "IdentityMapper instances cannot be combined with " \
586 "the BoundOpMapperMixin"
587
588
589 return expr
590
594
595 map_diff_base = map_mass_base
596 map_flux_base = map_mass_base
597 map_elementwise_max = map_mass_base
598 map_boundarize = map_mass_base
599 map_flux_exchange = map_mass_base
600
601 map_normal_component = map_mass_base
602
603
604
605
606 -class DependencyMapper(
607 CombineMapperMixin,
608 pymbolic.mapper.dependency.DependencyMapper,
609 OperatorReducerMixin):
610 - def __init__(self,
611 include_operator_bindings=True,
612 composite_leaves=None,
613 **kwargs):
614 if composite_leaves == False:
615 include_operator_bindings = False
616 if composite_leaves == True:
617 include_operator_bindings = True
618
619 pymbolic.mapper.dependency.DependencyMapper.__init__(self,
620 composite_leaves=composite_leaves, **kwargs)
621
622 self.include_operator_bindings = include_operator_bindings
623
629
632
635
638
639
640
641 -class FlopCounter(
642 CombineMapperMixin,
643 pymbolic.mapper.flop_counter.FlopCounter):
649
656
660
662 return not bool(self.dep_mapper(expr))
663
664
665
666
667 -class IdentityMapper(
668 IdentityMapperMixin,
669 pymbolic.mapper.IdentityMapper):
671
672
673
674
675
676 -class SubstitutionMapper(pymbolic.mapper.substitutor.SubstitutionMapper,
677 IdentityMapperMixin):
679
680
681
682
683 -class StringifyMapper(pymbolic.mapper.stringifier.StringifyMapper):
686
687 - def map_diff(self, expr, enclosing_prec):
688 return "Diff%d" % expr.xyz_axis
689
691 return "MInvST%d" % expr.xyz_axis
692
694 return "Stiff%d" % expr.xyz_axis
695
697 return "StiffT%d" % expr.xyz_axis
698
699 - def map_mass(self, expr, enclosing_prec):
701
704
705 - def map_flux(self, expr, enclosing_prec):
707
708 - def map_lift(self, expr, enclosing_prec):
710
713
715 return "Boundarize<tag=%s>" % expr.tag
716
718 return "FExch<idx=%d,rank=%d>" % (expr.index, expr.rank)
719
721 return "Normal<tag=%s>[%d]" % (expr.tag, expr.axis)
722
725
728
736 return self.rec(expr.child, enclosing_prec)
737
744
767
768
769
770
771 -class OperatorBinder(CSECachingMapperMixin, IdentityMapper):
786
795
800
820
822 return expr.__class__(tuple(self.rec(child) for child in expr.children))
823
825 def is_scalar(expr):
826 return isinstance(expr, (int, float, complex))
827
828 from pytools import len_iterable
829 nonscalar_count = len_iterable(ch
830 for ch in expr.children
831 if not is_scalar(ch))
832
833 if nonscalar_count > 1:
834
835 return expr
836 else:
837 def do_map(expr):
838 if is_scalar(expr):
839 return expr
840 else:
841 return self.rec(expr)
842 return expr.__class__(tuple(
843 do_map(child) for child in expr.children))
844
864
870 """Operates on L{FluxOperator} instances bound to L{BoundaryPair}s. If the
871 boundary pair's C{bfield} is an expression of what's available in the
872 C{field}, we can avoid fetching the data for the explicit boundary
873 condition and just substitute the C{bfield} expression into the flux. This
874 mapper does exactly that.
875 """
876
877 map_common_subexpression_uncached = \
878 IdentityMapper.map_common_subexpression
879
881 if not (isinstance(expr.op, FluxOperator)
882 and isinstance(expr.field, BoundaryPair)):
883 return IdentityMapper.map_operator_binding(self, expr)
884
885 bpair = expr.field
886 vol_field = bpair.field
887 bdry_field = bpair.bfield
888 flux = expr.op.flux
889
890 bdry_dependencies = DependencyMapper(
891 include_calls="descend_args",
892 include_operator_bindings=True)(bdry_field)
893
894 vol_dependencies = DependencyMapper(
895 include_operator_bindings=True)(vol_field)
896
897 vol_bdry_intersection = bdry_dependencies & vol_dependencies
898 if vol_bdry_intersection:
899 raise RuntimeError("Variables are being used as both "
900 "boundary and volume quantities: %s"
901 % ", ".join(str(v) for v in vol_bdry_intersection))
902
903
904
905
906 class MaxBoundaryFluxEvaluableExpressionFinder(
907 IdentityMapper, OperatorReducerMixin):
908 def __init__(self, vol_expr_list):
909 self.vol_expr_list = vol_expr_list
910 self.vol_expr_to_idx = dict((vol_expr, idx)
911 for idx, vol_expr in enumerate(vol_expr_list))
912
913 self.bdry_expr_list = []
914 self.bdry_expr_to_idx = {}
915
916 def register_boundary_expr(self, expr):
917 try:
918 return self.bdry_expr_to_idx[expr]
919 except KeyError:
920 idx = len(self.bdry_expr_to_idx)
921 self.bdry_expr_to_idx[expr] = idx
922 self.bdry_expr_list.append(expr)
923 return idx
924
925 def register_volume_expr(self, expr):
926 try:
927 return self.vol_expr_to_idx[expr]
928 except KeyError:
929 idx = len(self.vol_expr_to_idx)
930 self.vol_expr_to_idx[expr] = idx
931 self.vol_expr_list.append(expr)
932 return idx
933
934 def map_normal_component(self, expr):
935 if expr.tag != bpair.tag:
936 raise RuntimeError("BoundaryNormalComponent and BoundaryPair "
937 "do not agree about boundary tag: %s vs %s"
938 % (expr.tag, bpair.tag))
939
940 from hedge.flux import Normal
941 return Normal(expr.axis)
942
943 def map_variable(self, expr):
944 from hedge.flux import FieldComponent
945 return FieldComponent(
946 self.register_boundary_expr(expr),
947 is_local=False)
948
949 map_subscript = map_variable
950
951 def map_operator_binding(self, expr):
952 from hedge.flux import FieldComponent
953 if isinstance(expr.op, BoundarizeOperator):
954 if expr.op.tag != bpair.tag:
955 raise RuntimeError("BoundarizeOperator and BoundaryPair "
956 "do not agree about boundary tag: %s vs %s"
957 % (expr.op.tag, bpair.tag))
958
959 return FieldComponent(
960 self.register_volume_expr(expr.field),
961 is_local=True)
962 elif isinstance(expr.op, FluxExchangeOperator):
963 from hedge.mesh import TAG_RANK_BOUNDARY
964 op_tag = TAG_RANK_BOUNDARY(expr.op.rank)
965 if bpair.tag != op_tag:
966 raise RuntimeError("BoundarizeOperator and FluxExchangeOperator "
967 "do not agree about boundary tag: %s vs %s"
968 % (op_tag, bpair.tag))
969 return FieldComponent(
970 self.register_boundary_expr(expr),
971 is_local=False)
972 else:
973 raise RuntimeError("Found '%s' in a boundary term. "
974 "To the best of my knowledge, no hedge operator applies "
975 "directly to boundary data, so this is likely in error."
976 % expr.op)
977
978 from hedge.tools import is_obj_array
979 if not is_obj_array(vol_field):
980 vol_field = [vol_field]
981
982 mbfeef = MaxBoundaryFluxEvaluableExpressionFinder(vol_field)
983 new_bdry_field = mbfeef(bdry_field)
984
985
986 from hedge.flux import FluxSubstitutionMapper, FieldComponent
987
988 def sub_bdry_into_flux(expr):
989 if isinstance(expr, FieldComponent) and not expr.is_local:
990 if expr.index == 0 and not is_obj_array(bdry_field):
991 return new_bdry_field
992 else:
993 return new_bdry_field[expr.index]
994 else:
995 return None
996
997 new_flux = FluxSubstitutionMapper(
998 sub_bdry_into_flux)(flux)
999
1000 from hedge.tools import is_zero
1001 if is_zero(new_flux):
1002 return 0
1003 else:
1004 return OperatorBinding(
1005 FluxOperator(new_flux), BoundaryPair(
1006 numpy.array(mbfeef.vol_expr_list, dtype=object),
1007 numpy.array(mbfeef.bdry_expr_list, dtype=object),
1008 bpair.tag))
1009
1010
1011
1012
1013
1014 -class CollectorMixin(LocalOpReducerMixin, FluxOpReducerMixin):
1016 from pytools import flatten
1017 return set(flatten(values))
1018
1021
1024
1027
1030
1033
1036
1039
1040
1041
1042
1043 -class FluxCollector(CSECachingMapperMixin, CollectorMixin, CombineMapper):
1055
1061 return set([bpair.tag])
1062
1080
1081
1082
1083
1084 -class Evaluator(pymbolic.mapper.evaluator.EvaluationMapper):
1087
1094 class IndexGroupKillerSubstMap:
1095 def __init__(self, kill_set):
1096 self.kill_set = kill_set
1097
1098 def __call__(self, expr):
1099 if expr in kill_set:
1100 return 0
1101 else:
1102 return None
1103
1104
1105
1106 killers = []
1107 for i in range(len(index_groups)):
1108 kill_set = set()
1109 for j in range(len(index_groups)):
1110 if i != j:
1111 kill_set |= set(index_groups[j])
1112
1113 killers.append(IndexGroupKillerSubstMap(kill_set))
1114
1115 from hedge.optemplate import \
1116 SubstitutionMapper, \
1117 CommutativeConstantFoldingMapper
1118
1119 return [
1120 CommutativeConstantFoldingMapper()(
1121 SubstitutionMapper(killer)(
1122 op_template[ig]))
1123 for ig in index_groups
1124 for killer in killers]
1125