|
21 | 21 | from devito.finite_differences.tools import coeff_priority, make_shift_x0 |
22 | 22 | from devito.logger import warning |
23 | 23 | from devito.tools import ( |
24 | | - Tag, as_tuple, extract_dtype, filter_ordered, flatten, frozendict, infer_dtype, |
25 | | - is_integer, is_number, memoized_func, split |
| 24 | + Pickable, Tag, as_tuple, extract_dtype, filter_ordered, flatten, frozendict, |
| 25 | + infer_dtype, is_integer, is_number, memoized_func, split |
26 | 26 | ) |
27 | 27 | from devito.types import Array, DimensionTuple, Evaluable, StencilDimension |
28 | 28 | from devito.types.basic import AbstractFunction, Indexed |
|
35 | 35 | 'Imag', |
36 | 36 | 'IndexDerivative', |
37 | 37 | 'IndexDerivativeProperty', |
| 38 | + 'LocalSum', |
38 | 39 | 'Real', |
39 | 40 | 'Weights', |
40 | 41 | ] |
@@ -940,13 +941,106 @@ def _evaluate(self, **kwargs): |
940 | 941 | terms.append(expr.xreplace(mapper)) |
941 | 942 | return sum(terms) |
942 | 943 |
|
| 944 | + @property |
| 945 | + def bound_symbols(self): |
| 946 | + return set(self.dimensions) |
| 947 | + |
943 | 948 | @property |
944 | 949 | def free_symbols(self): |
945 | | - return super().free_symbols - set(self.dimensions) |
| 950 | + return super().free_symbols - self.bound_symbols |
946 | 951 |
|
947 | 952 | func = DifferentiableOp._rebuild |
948 | 953 |
|
949 | 954 |
|
| 955 | +class LocalSum(IndexSum, Pickable): |
| 956 | + |
| 957 | + """ |
| 958 | + A zero-initialized sum over guarded local dimensions. |
| 959 | +
|
| 960 | + `cdims` are guarded ConditionalDimensions, retained with their original |
| 961 | + parents and conditions. `dimensions` exposes the parent iteration dimensions. |
| 962 | + Masked points contribute zero. The sum remains symbolic until Cluster lowering |
| 963 | + chooses its implementation. |
| 964 | +
|
| 965 | + Examples |
| 966 | + -------- |
| 967 | + For bilinear interpolation, `posx` and `posy` are the grid indices of sparse |
| 968 | + point `p`, and `wx` and `wy` hold its interpolation weights:: |
| 969 | +
|
| 970 | + i = CustomDimension('i', 0, 1, 2) |
| 971 | + j = CustomDimension('j', 0, 1, 2) |
| 972 | + ci = ConditionalDimension('i', i, indirect=True, |
| 973 | + condition=And(posx + i >= x_m, posx + i <= x_M)) |
| 974 | + cj = ConditionalDimension('j', j, indirect=True, |
| 975 | + condition=And(posy + j >= y_m, posy + j <= y_M)) |
| 976 | + value = LocalSum( |
| 977 | + wx[p, ci]*wy[p, cj]*f[posx + ci, posy + cj], |
| 978 | + cdims=(ci, cj) |
| 979 | + ) |
| 980 | + Eq(rcv[p], value) |
| 981 | +
|
| 982 | + The scalar lowering has the following semantics (pseudocode):: |
| 983 | +
|
| 984 | + acc = 0 |
| 985 | + for i in range(2): |
| 986 | + for j in range(2): |
| 987 | + if x_m <= posx + i <= x_M and y_m <= posy + j <= y_M: |
| 988 | + acc += wx[p, i]*wy[p, j]*f[posx + i, posy + j] |
| 989 | + rcv[p] = acc |
| 990 | +
|
| 991 | + The guarded indices and their parents are local to the sum; `p` remains an |
| 992 | + outer iteration dimension. |
| 993 | + If every tap is masked, `rcv[p]` receives zero. |
| 994 | + """ |
| 995 | + |
| 996 | + __rargs__ = ('expr',) |
| 997 | + __rkwargs__ = ('cdims', 'dtype') |
| 998 | + |
| 999 | + def __new__(cls, expr, cdims=(), dtype=None, **kwargs): |
| 1000 | + obj = sympy.Expr.__new__(cls, expr) |
| 1001 | + |
| 1002 | + obj._expr = expr |
| 1003 | + obj._cdims = as_tuple(cdims) |
| 1004 | + obj._dtype = dtype |
| 1005 | + |
| 1006 | + return obj |
| 1007 | + |
| 1008 | + def _hashable_content(self): |
| 1009 | + return super()._hashable_content() + (self.cdims, self.dtype) |
| 1010 | + |
| 1011 | + @property |
| 1012 | + def cdims(self): |
| 1013 | + return self._cdims |
| 1014 | + |
| 1015 | + @cached_property |
| 1016 | + def dtype(self): |
| 1017 | + if self._dtype is None: |
| 1018 | + return extract_dtype(self.expr) |
| 1019 | + return self._dtype |
| 1020 | + |
| 1021 | + @cached_property |
| 1022 | + def dimensions(self): |
| 1023 | + return tuple(d.parent for d in self.cdims) |
| 1024 | + |
| 1025 | + @cached_property |
| 1026 | + def conditionals(self): |
| 1027 | + return frozendict({d: d.condition for d in self.cdims}) |
| 1028 | + |
| 1029 | + @property |
| 1030 | + def bound_symbols(self): |
| 1031 | + return super().bound_symbols | set(self.cdims) |
| 1032 | + |
| 1033 | + @property |
| 1034 | + def free_symbols(self): |
| 1035 | + symbols = self.expr.free_symbols.union(*[d.free_symbols for d in self.cdims]) |
| 1036 | + return symbols - self.bound_symbols |
| 1037 | + |
| 1038 | + def _evaluate(self, **kwargs): |
| 1039 | + return self._rebuild(*self._evaluate_args(**kwargs)) |
| 1040 | + |
| 1041 | + __reduce_ex__ = Pickable.__reduce_ex__ |
| 1042 | + |
| 1043 | + |
950 | 1044 | class WeightsIndexed(Indexed): |
951 | 1045 |
|
952 | 1046 | @property |
|
0 commit comments