def coefficients_from_source(self, avec, bvec): from sumpy.kernel import DirectionalSourceDerivative kernel = self.kernel from sumpy.tools import mi_power, mi_factorial if isinstance(kernel, DirectionalSourceDerivative): if kernel.get_base_kernel() is not kernel.inner_kernel: raise NotImplementedError("more than one source derivative " "not supported at present") from sumpy.symbolic import make_sympy_vector dir_vec = make_sympy_vector(kernel.dir_vec_name, kernel.dim) coeff_identifiers = self.get_full_coefficient_identifiers() result = [0] * len(coeff_identifiers) for idim in range(kernel.dim): for i, mi in enumerate(coeff_identifiers): if mi[idim] == 0: continue derivative_mi = tuple(mi_i - 1 if iaxis == idim else mi_i for iaxis, mi_i in enumerate(mi)) result[i] += ( - mi_power(avec, derivative_mi) * mi[idim] * dir_vec[idim]) for i, mi in enumerate(coeff_identifiers): result[i] /= mi_factorial(mi) else: result = [ mi_power(avec, mi) / mi_factorial(mi) for mi in self.get_full_coefficient_identifiers()] return self.full_to_stored(result)
def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale): if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to " "Taylor multipole expansion" % type(src_expansion).__name__) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) from sumpy.tools import mi_factorial src_mi_to_index = dict((mi, i) for i, mi in enumerate( src_expansion.get_coefficient_identifiers())) for i, mi in enumerate(src_expansion.get_coefficient_identifiers()): src_coeff_exprs[i] *= mi_factorial(mi) result = [0] * len(self.get_full_coefficient_identifiers()) from pytools import generate_nonnegative_integer_tuples_below as gnitb for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi) for src_mi in gnitb(tgt_mi_plus_one): try: src_index = src_mi_to_index[src_mi] except KeyError: # Omitted coefficients: not life-threatening continue contrib = src_coeff_exprs[src_index] for idim in range(self.dim): n = tgt_mi[idim] k = src_mi[idim] assert n >= k from sympy import binomial contrib *= (binomial(n, k) * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k)) result[i] += ( contrib * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi)) result[i] /= mi_factorial(tgt_mi) logger.info("building translation operator: done") return ( self.derivative_wrangler.get_stored_mpole_coefficients_from_full( result, tgt_rscale))
def coefficients_from_source(self, avec, bvec, rscale, sac=None): from sumpy.kernel import DirectionalSourceDerivative kernel = self.kernel from sumpy.tools import mi_power, mi_factorial if not self.use_rscale: rscale = 1 if isinstance(kernel, DirectionalSourceDerivative): from sumpy.symbolic import make_sym_vector dir_vecs = [] tmp_kernel = kernel while isinstance(tmp_kernel, DirectionalSourceDerivative): dir_vecs.append( make_sym_vector(tmp_kernel.dir_vec_name, kernel.dim)) tmp_kernel = tmp_kernel.inner_kernel if kernel.get_base_kernel() is not tmp_kernel: raise NotImplementedError("Unknown kernel wrapper.") nderivs = len(dir_vecs) coeff_identifiers = self.get_full_coefficient_identifiers() result = [0] * len(coeff_identifiers) for i, mi in enumerate(coeff_identifiers): # One source derivative is the dot product of the gradient and # directional vector. # For multiple derivatives, gradient of gradients is taken. # For eg: in 3D, 2 source derivatives gives 9 terms and # cartesian_product below enumerates these 9 terms. for deriv_terms in cartesian_product(*[range(kernel.dim)] * nderivs): prod = 1 derivative_mi = list(mi) for nderivative, deriv_dim in enumerate(deriv_terms): prod *= -derivative_mi[deriv_dim] prod *= dir_vecs[nderivative][deriv_dim] derivative_mi[deriv_dim] -= 1 if any(v < 0 for v in derivative_mi): continue result[i] += mi_power(avec, derivative_mi) * prod for i, mi in enumerate(coeff_identifiers): result[i] /= (mi_factorial(mi) * rscale**sum(mi)) else: avec = [sym.UnevaluatedExpr(a * rscale**-1) for a in avec] result = [ mi_power(avec, mi) / mi_factorial(mi) for mi in self.get_full_coefficient_identifiers() ] return (self.expansion_terms_wrangler. get_stored_mpole_coefficients_from_full(result, rscale, sac=sac))
def evaluate(self, coeffs, bvec): from sumpy.tools import mi_power, mi_factorial evaluated_coeffs = self.stored_to_full(coeffs) result = sum( coeff * self.kernel.postprocess_at_target(mi_power(bvec, mi), bvec) / mi_factorial(mi) for coeff, mi in zip(evaluated_coeffs, self.get_full_coefficient_identifiers()) ) return result
def evaluate(self, coeffs, bvec, rscale): from sumpy.tools import mi_power, mi_factorial evaluated_coeffs = ( self.derivative_wrangler.get_full_kernel_derivatives_from_stored( coeffs, rscale)) bvec = bvec * rscale**-1 result = sum( coeff * mi_power(bvec, mi) / mi_factorial(mi) for coeff, mi in zip( evaluated_coeffs, self.get_full_coefficient_identifiers())) return result
def coefficients_from_source(self, avec, bvec, rscale): from sumpy.kernel import DirectionalSourceDerivative kernel = self.kernel from sumpy.tools import mi_power, mi_factorial if not self.use_rscale: rscale = 1 if isinstance(kernel, DirectionalSourceDerivative): if kernel.get_base_kernel() is not kernel.inner_kernel: raise NotImplementedError("more than one source derivative " "not supported at present") from sumpy.symbolic import make_sym_vector dir_vec = make_sym_vector(kernel.dir_vec_name, kernel.dim) coeff_identifiers = self.get_full_coefficient_identifiers() result = [0] * len(coeff_identifiers) for idim in range(kernel.dim): for i, mi in enumerate(coeff_identifiers): if mi[idim] == 0: continue derivative_mi = tuple(mi_i - 1 if iaxis == idim else mi_i for iaxis, mi_i in enumerate(mi)) result[i] += ( - mi_power(avec, derivative_mi) * mi[idim] * dir_vec[idim]) for i, mi in enumerate(coeff_identifiers): result[i] /= (mi_factorial(mi) * rscale ** sum(mi)) else: avec = avec * rscale**-1 result = [ mi_power(avec, mi) / mi_factorial(mi) for mi in self.get_full_coefficient_identifiers()] return ( self.derivative_wrangler.get_stored_mpole_coefficients_from_full( result, rscale))
def evaluate(self, coeffs, bvec, rscale): from sumpy.tools import mi_power, mi_factorial evaluated_coeffs = ( self.derivative_wrangler.get_full_kernel_derivatives_from_stored( coeffs, rscale)) bvec = bvec * rscale**-1 result = sum( coeff * mi_power(bvec, mi, evaluate=False) / mi_factorial(mi) for coeff, mi in zip( evaluated_coeffs, self.get_full_coefficient_identifiers())) return result
def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac=None, _fast_version=True): if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to " "Taylor multipole expansion" % type(src_expansion).__name__) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) from sumpy.tools import mi_factorial src_mi_to_index = {mi: i for i, mi in enumerate( src_expansion.get_coefficient_identifiers())} tgt_mi_to_index = {mi: i for i, mi in enumerate( self.get_full_coefficient_identifiers())} # This algorithm uses the observation that M2M coefficients # have the following form in 2D # # $T_{m, n} = \sum_{i\le m, j\le n} C_{i, j} # d_x^i d_y^j \binom{m}{i} \binom{n}{j}$ # and can be rewritten as follows. # # Let $Y_{m, n} = \sum_{i\le m} C_{i, n} d_x^i \binom{m}{i}$. # # Then, $T_{m, n} = \sum_{j\le n} Y_{m, j} d_y^j \binom{n}{j}$. # # $Y_{m, n}$ are $p^2$ temporary variables that are # reused for different M2M coefficients and costs $p$ per variable. # Total cost for calculating $Y_{m, n}$ is $p^3$ and similar # for $T_{m, n}$. For compressed Taylor series this can be done # more efficiently. # Let's take the example u_xy + u_x + u_y = 0. # In the diagram below, C depicts a non zero source coefficient. # We divide these into two hyperplanes. # # C C 0 # C 0 C 0 0 0 # C 0 0 = C 0 0 + 0 0 0 # C 0 0 0 C 0 0 0 0 0 0 0 # C C C C C C 0 0 0 0 0 C C C C # # The calculations done when naively translating first hyperplane of the # source coefficients (C) to target coefficients (T) are shown # below in the graph. Each connection represents a O(1) calculation, # and the arrows go "up and to the right". # # ┌─→C T # │ ↑ # │┌→C→0←─────┐-> T T # ││ ↑ ↑ │ # ││ ┌─┘┌────┐│ # ││↱C→0↲0←─┐││ T T T # │││└───⬏ │││ # └└└C→0 0 0│││ T T T T # └───⬏ ↑│││ # └─────┘│││ # └──────┘││ # └───────┘│ # └────────┘ # # By using temporaries (Y), this can be reduced as shown below. # # ┌→C Y T # │ ↑ # │↱C 0 -> Y→0 -> T T # ││↑ # ││C 0 0 Y→0 0 T T T # ││↑ └───⬏ # └└C 0 0 0 Y 0 0 0 T T T T # └───⬏ ↑ # └─────┘ # # Note that in the above calculation data is propagated upwards # in the first pass and then rightwards in the second pass. # Data propagation with zeros are not shown as they are not calculated. # If the propagation was done rightwards first and upwards second # number of calculations are higher as shown below. # # C ┌→Y T # │ ↑ # C→0 -> │↱Y↱Y -> T T # ││↑│↑ # C→0 0 ││Y│Y Y T T T # └───⬏ ││↑│↑ ↑ # C→0 0 0 └└Y└Y Y Y T T T T # └───⬏ ↑ # └─────┘ # # For the second hyperplane, data is propogated rightwards first # and then upwards second which is opposite to that of the first # hyperplane. # # 0 0 0 # # 0 0 -> 0↱0 -> 0 T # │↑ # 0 0 0 0│0 0 0 T T # │↑ ↑ # 0 C→C→C 0└Y Y Y 0 T T T # └───⬏ # # In other words, we're better off computing the translation # one dimension at a time. If the coefficient-identifying multi-indices # in the source expansion have the form (0, m) and (n, 0), where m>=0, n>=1, # then we calculate the output from (0, m) with the second # dimension as the fastest varying dimension and then calculate # the output from (n, 0) with the first dimension as the fastest # varying dimension. tgt_hyperplanes = \ self.expansion_terms_wrangler._split_coeffs_into_hyperplanes() result = [0] * len(self.get_full_coefficient_identifiers()) # axis morally iterates over 'hyperplane directions' for axis in range(self.dim): # {{{ index gymnastics # First, let's write source coefficients in target coefficient # indices. If target order is lower than source order, then # we will discard higher order terms from source coefficients. cur_dim_input_coeffs = \ [0] * len(self.get_full_coefficient_identifiers()) for d, mis in tgt_hyperplanes: # Only consider hyperplanes perpendicular to *axis*. if d != axis: continue for mi in mis: # When target order is higher than source order, we assume # that the higher order source coefficients were zero. if mi not in src_mi_to_index: continue src_idx = src_mi_to_index[mi] tgt_idx = tgt_mi_to_index[mi] cur_dim_input_coeffs[tgt_idx] = src_coeff_exprs[src_idx] * \ sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(mi) if all(coeff == 0 for coeff in cur_dim_input_coeffs): continue # }}} # {{{ translation # As explained above using the unicode art, we use the orthogonal axis # as the last dimension to vary to reduce the number of operations. dims = list(range(axis)) + \ list(range(axis+1, self.dim)) + [axis] # d is the axis along which we translate. for d in dims: # We build the full target multipole and then compress it # at the very end. cur_dim_output_coeffs = \ [0] * len(self.get_full_coefficient_identifiers()) for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): # Calling this input_mis instead of src_mis because we # converted the source coefficients to target coefficient # indices beforehand. for mi_i in range(tgt_mi[d]+1): input_mi = mi_set_axis(tgt_mi, d, mi_i) contrib = cur_dim_input_coeffs[tgt_mi_to_index[input_mi]] for n, k, dist in zip(tgt_mi, input_mi, dvec): assert n >= k contrib /= factorial(n-k) contrib *= \ sym.UnevaluatedExpr(dist/tgt_rscale)**(n-k) cur_dim_output_coeffs[i] += contrib # cur_dim_output_coeffs is the input in the next iteration cur_dim_input_coeffs = cur_dim_output_coeffs # }}} for i in range(len(cur_dim_output_coeffs)): result[i] += cur_dim_output_coeffs[i] # {{{ simpler, functionally equivalent code if not _fast_version: src_mi_to_index = dict((mi, i) for i, mi in enumerate( src_expansion.get_coefficient_identifiers())) result = [0] * len(self.get_full_coefficient_identifiers()) for i, mi in enumerate(src_expansion.get_coefficient_identifiers()): src_coeff_exprs[i] *= mi_factorial(mi) from pytools import generate_nonnegative_integer_tuples_below as gnitb for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi) for src_mi in gnitb(tgt_mi_plus_one): try: src_index = src_mi_to_index[src_mi] except KeyError: # Omitted coefficients: not life-threatening continue contrib = src_coeff_exprs[src_index] for idim in range(self.dim): n = tgt_mi[idim] k = src_mi[idim] assert n >= k from sympy import binomial contrib *= (binomial(n, k) * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k)) result[i] += (contrib * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi)) result[i] /= mi_factorial(tgt_mi) # }}} logger.info("building translation operator: done") return ( self.expansion_terms_wrangler.get_stored_mpole_coefficients_from_full( result, tgt_rscale, sac=sac))
def evaluate(self, coeffs, bvec, rscale): from pytools import factorial evaluated_coeffs = ( self.derivative_wrangler.get_full_kernel_derivatives_from_stored( coeffs, rscale)) bvec = [b * rscale**-1 for b in bvec] mi_to_index = dict( (mi, i) for i, mi in enumerate(self.get_full_coefficient_identifiers())) # Sort multi-indices so that last dimension varies fastest sorted_target_mis = sorted(self.get_full_coefficient_identifiers()) dim = self.dim # Start with an invalid "seen" multi-index seen_mi = [-1] * dim # Local sum keep the sum of the terms that differ by each dimension local_sum = [0] * dim # Local multiplier keep the scalar that the sum has to be multiplied by # when adding to the sum of the preceding dimension. local_multiplier = [0] * dim # For the multi-indices in 3D, local_sum looks like this: # # Multi-index | coef | local_sum | local_mult # (0, 0, 0) | c0 | 0, 0, c0 | 0, 1, 1 # (0, 0, 1) | c1 | 0, 0, c0+c1*dz | 0, 1, 1 # (0, 0, 2) | c2 | 0, 0, c0+c1*dz+c2*dz^2 | 0, 1, 1 # (0, 1, 0) | c3 | 0, c0+c1*dz+c2*dz^2, c3 | 0, 1, dy # (0, 1, 1) | c4 | 0, c0+c1*dz+c2*dz^2, c3+c4*dz | 0, 1, dy # (0, 1, 2) | c5 | 0, c0+c1*dz+c2*dz^2, c3+c4*dz+c5*dz^2 | 0, 1, dy # (0, 2, 0) | c6 | 0, c0+c1*dz+c2*dz^2, c6 | 0, 1, dy^2 # | | +dy*(c3+c4*dz+c5*dz^2) | # (0, 2, 1) | c7 | 0, c0+c1*dz+c2*dz^2, c6+c7*dz | 0, 1, dy^2 # | | +dy*(c3+c4*dz+c5*dz^2) | # (0, 2, 2) | c8 | 0, c0+c1*dz+c2*dz^2, c6+c7*dz+x8*dz^2 | 0, 1, dy^2 # | | +dy*(c3+c4*dz+c5*dz^2) | # (1, 0, 0) | c8 | c0+c1*dz+c2*dz^2, 0, 0 | 0, dx, 1 # | | +dy*(c3+c4*dz+c5*dz^2) | # | | +dy^2*(c6+c7*dz+c8*dz^2) | for mi in sorted_target_mis: # {{{ handle the case where a not-last dimension "clicked over" # (where d will be that not-last dimension) # Iterate in reverse order of dimensions to properly handle a # "double click-over". for d in reversed(range(dim - 1)): if seen_mi[d] != mi[d]: # If the dimension d of mi changed from the previous value # then the sum for dimension d+1 is complete, add it to # dimension d after multiplying and restart. local_sum[d] += local_sum[d + 1] * local_multiplier[d + 1] local_sum[d + 1] = 0 local_multiplier[d + 1] = bvec[d]**mi[d] / factorial(mi[d]) # }}} local_sum[dim-1] += evaluated_coeffs[mi_to_index[mi]] * \ bvec[dim-1]**mi[dim-1] / factorial(mi[dim-1]) seen_mi = mi for d in reversed(range(dim - 1)): local_sum[d] += local_sum[d + 1] * local_multiplier[d + 1] # {{{ simpler, functionally equivalent code if 0: from sumpy.tools import mi_power, mi_factorial return sum( coeff * mi_power(bvec, mi, evaluate=False) / mi_factorial(mi) for coeff, mi in zip(evaluated_coeffs, self.get_full_coefficient_identifiers())) # }}} return local_sum[0]
def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac=None): if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to " "Taylor multipole expansion" % type(src_expansion).__name__) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) from sumpy.tools import mi_factorial src_mi_to_index = dict((mi, i) for i, mi in enumerate( src_expansion.get_coefficient_identifiers())) tgt_mi_to_index = dict( (mi, i) for i, mi in enumerate(self.get_full_coefficient_identifiers())) src_coeff_exprs = list(src_coeff_exprs) for i, mi in enumerate(src_expansion.get_coefficient_identifiers()): src_coeff_exprs[i] *= sym.UnevaluatedExpr(src_rscale / tgt_rscale)**sum(mi) result = [0] * len(self.get_full_coefficient_identifiers()) # This algorithm uses the observation that M2M coefficients # have the following form in 2D # # $B_{m, n} = \sum_{i\le m, j\le n} A_{i, j} # d_x^i d_y^j \binom{m}{i} \binom{n}{j}$ # and can be rewritten as follows. # # Let $T_{m, n} = \sum_{i\le m} A_{i, n} d_x^i \binom{m}{i}$. # # Then, $B_{m, n} = \sum_{j\le n} T_{m, j} d_y^j \binom{n}{j}$. # # $T_{m, n}$ are $p^2$ number of temporary variables that are # reused for different M2M coefficients and costs $p$ per variable. # Total cost for calculating $T_{m, n}$ is $p^3$ and similar # for $B_{m, n}$ # In other words, we're better off computing the translation # one dimension at a time. This is realized here by using # the output from one dimension as the input to the next. # per_dim_coeffs_to_translate serves as the array of input # coefficients for each dimension's translation. dim_coeffs_to_translate = src_coeff_exprs mi_to_index = src_mi_to_index for d in range(self.dim): result = [0] * len(self.get_full_coefficient_identifiers()) for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): src_mis_per_dim = [] for mi_i in range(tgt_mi[d] + 1): new_mi = list(tgt_mi) new_mi[d] = mi_i src_mis_per_dim.append(tuple(new_mi)) for src_mi in src_mis_per_dim: try: src_index = mi_to_index[src_mi] except KeyError: # Omitted coefficients: not life-threatening continue contrib = dim_coeffs_to_translate[src_index] for idim in range(self.dim): n = tgt_mi[idim] k = src_mi[idim] assert n >= k contrib /= mi_factorial((n - k, )) contrib *= sym.UnevaluatedExpr(dvec[idim] / tgt_rscale)**(n - k) result[i] += contrib dim_coeffs_to_translate = result[:] mi_to_index = tgt_mi_to_index logger.info("building translation operator: done") return (self.expansion_terms_wrangler. get_stored_mpole_coefficients_from_full(result, tgt_rscale, sac=sac))