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 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 = [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.derivative_wrangler.get_stored_mpole_coefficients_from_full( result, rscale))
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 translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale): logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansionBase if isinstance(src_expansion, VolumeTaylorMultipoleExpansionBase): # We know the general form of the multipole expansion is: # # coeff0 * diff(kernel, mi0) + coeff1 * diff(kernel, mi1) + ... # # To get the local expansion coefficients, we take derivatives of # the multipole expansion. # # This code speeds up derivative taking by caching all kernel # derivatives. taker = src_expansion.get_kernel_derivative_taker(dvec) from sumpy.tools import add_mi result = [] for deriv in self.get_coefficient_identifiers(): local_result = [] for coeff, term in zip( src_coeff_exprs, src_expansion.get_coefficient_identifiers()): kernel_deriv = (src_expansion.get_scaled_multipole( taker.diff(add_mi(deriv, term)), dvec, src_rscale, nderivatives=sum(deriv) + sum(term), nderivatives_for_scaling=sum(term))) local_result.append(coeff * kernel_deriv * tgt_rscale**sum(deriv)) result.append(sym.Add(*local_result)) else: from sumpy.tools import MiDerivativeTaker # Rscale/operand magnitude is fairly sensitive to the order of # operations--which is something we don't have fantastic control # over at the symbolic level. Scaling dvec, then differentiating, # and finally rescaling dvec leaves the expression needing a scaling # to compensate for differentiating which is done at the end. # This moves the two cancelling "rscales" closer to each other at # the end in the hope of helping rscale magnitude. dvec_scaled = [d * src_rscale for d in dvec] expr = src_expansion.evaluate(src_coeff_exprs, dvec_scaled, rscale=src_rscale) replace_dict = dict((d, d / src_rscale) for d in dvec) taker = MiDerivativeTaker(expr, dvec) rscale_ratio = sym.UnevaluatedExpr(tgt_rscale / src_rscale) result = [ (taker.diff(mi).xreplace(replace_dict) * rscale_ratio**sum(mi)) for mi in self.get_coefficient_identifiers() ] logger.info("building translation operator: done") return result
def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac=None): logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansionBase if isinstance(src_expansion, VolumeTaylorMultipoleExpansionBase): # We know the general form of the multipole expansion is: # # coeff0 * diff(kernel, mi0) + coeff1 * diff(kernel, mi1) + ... # # To get the local expansion coefficients, we take derivatives of # the multipole expansion. # # This code speeds up derivative taking by caching all kernel # derivatives. taker = src_expansion.get_kernel_derivative_taker(dvec) from sumpy.tools import add_mi # Calculate a elementwise maximum multi-index because the number # of multi-indices needed is much less than # gnitstam(src_order + tgt order) when PDE conforming expansions # are used. For full Taylor, there's no difference. def calc_max_mi(mis): return (max(mi[i] for mi in mis) for i in range(self.dim)) src_max_mi = calc_max_mi( src_expansion.get_coefficient_identifiers()) tgt_max_mi = calc_max_mi(self.get_coefficient_identifiers()) max_mi = add_mi(src_max_mi, tgt_max_mi) # Create a expansion terms wrangler for derivatives up to order # (tgt order)+(src order) including a corresponding reduction matrix tgtplusderiv_exp_terms_wrangler = \ src_expansion.expansion_terms_wrangler.copy( order=self.order + src_expansion.order, max_mi=tuple(max_mi)) tgtplusderiv_coeff_ids = \ tgtplusderiv_exp_terms_wrangler.get_coefficient_identifiers() tgtplusderiv_full_coeff_ids = \ tgtplusderiv_exp_terms_wrangler.get_full_coefficient_identifiers() tgtplusderiv_ident_to_index = { ident: i for i, ident in enumerate(tgtplusderiv_full_coeff_ids) } result = [] for lexp_mi in self.get_coefficient_identifiers(): lexp_mi_terms = [] # Embed the source coefficients in the coefficient array # for the (tgt order)+(src order) wrangler, offset by lexp_mi. embedded_coeffs = [0] * len(tgtplusderiv_full_coeff_ids) for coeff, term in zip( src_coeff_exprs, src_expansion.get_coefficient_identifiers()): embedded_coeffs[ tgtplusderiv_ident_to_index[add_mi(lexp_mi, term)]] \ = coeff # Compress the embedded coefficient set stored_coeffs = tgtplusderiv_exp_terms_wrangler \ .get_stored_mpole_coefficients_from_full( embedded_coeffs, src_rscale, sac=sac) # Sum the embedded coefficient set for tgtplusderiv_coeff_id, coeff in zip( tgtplusderiv_coeff_ids, stored_coeffs): if coeff == 0: continue nderivatives_for_scaling = \ sum(tgtplusderiv_coeff_id)-sum(lexp_mi) kernel_deriv = (src_expansion.get_scaled_multipole( taker.diff(tgtplusderiv_coeff_id), dvec, src_rscale, nderivatives=sum(tgtplusderiv_coeff_id), nderivatives_for_scaling=nderivatives_for_scaling)) lexp_mi_terms.append(coeff * kernel_deriv * tgt_rscale**sum(lexp_mi)) result.append(sym.Add(*lexp_mi_terms)) else: from sumpy.tools import MiDerivativeTaker # Rscale/operand magnitude is fairly sensitive to the order of # operations--which is something we don't have fantastic control # over at the symbolic level. Scaling dvec, then differentiating, # and finally rescaling dvec leaves the expression needing a scaling # to compensate for differentiating which is done at the end. # This moves the two cancelling "rscales" closer to each other at # the end in the hope of helping rscale magnitude. dvec_scaled = [d * src_rscale for d in dvec] expr = src_expansion.evaluate(src_coeff_exprs, dvec_scaled, rscale=src_rscale, sac=sac) replace_dict = dict((d, d / src_rscale) for d in dvec) taker = MiDerivativeTaker(expr, dvec) rscale_ratio = sym.UnevaluatedExpr(tgt_rscale / src_rscale) result = [ (taker.diff(mi).xreplace(replace_dict) * rscale_ratio**sum(mi)) for mi in self.get_coefficient_identifiers() ] logger.info("building translation operator: done") return result
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))