diff --git a/sumpy/e2e.py b/sumpy/e2e.py index 12e80b5f5d79781ab7ff78bcad8954e3e6ef3271..c0994444eee52fd94190f4a86d2d6f66aa469980 100644 --- a/sumpy/e2e.py +++ b/sumpy/e2e.py @@ -111,7 +111,7 @@ class E2EBase(KernelCacheWrapper): for i, coeff_i in enumerate( self.tgt_expansion.translate_from( self.src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale))] + dvec, tgt_rscale, sac=sac))] sac.run_global_cse() diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py index e8bfe84e1619c4fddb68a5dc7196ada2503fb6ec..26c52026e525a9d1c33925b03c4f159b5bc189a0 100644 --- a/sumpy/expansion/__init__.py +++ b/sumpy/expansion/__init__.py @@ -136,7 +136,7 @@ class ExpansionBase(object): raise NotImplementedError def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale): + dvec, tgt_rscale, sac=None): raise NotImplementedError def update_persistent_hash(self, key_hash, key_builder): @@ -227,30 +227,58 @@ class FullExpansionTermsWrangler(ExpansionTermsWrangler): # {{{ sparse matrix-vector multiplication -def _fast_spmv(reconstruct_matrix, vec, sac, transpose=False): - if not transpose: - res = [0] * len(reconstruct_matrix) +class CSEMatVec(object): + """ + A class to facilitate a fast matrix vector multiplication with + common subexpression eliminated. In compressed Taylor + series, the compression matrix's operation count can be + reduced to `O(p**d)` from `O(p**{2d-1})` using CSE. + + .. attribute:: assignments + + A list of tuples where the first argument of the tuple + is a row and the second argument is a list. If the + second argument is empty, the row is equal to the first + unused element from the vector. If not, the second + argument gives a list of tuples indicating a linear + combination of previously calculated row. + + The matrix represented is a square matrix with the number + of rows equal to the length of the `assignments` list. + """ + + def __init__(self, assignments): + self.assignments = assignments + + def matvec(self, vec, sac): + res = [0] * len(self.assignments) stored_idx = 0 - for row, deps in enumerate(reconstruct_matrix): + for row, deps in enumerate(self.assignments): if len(deps) == 0: res[row] = vec[stored_idx] stored_idx += 1 else: for k, v in deps: res[row] += res[k] * v - new_sym = sym.Symbol(sac.assign_unique("decompress_temp", res[row])) - res[row] = new_sym + if sac is not None: + new_sym = sym.Symbol(sac.assign_unique("projection_temp", res[row])) + res[row] = new_sym return res - else: + + def transpose_matvec(self, vec, sac): res = [] expr_all = list(vec) - for row, deps in reversed(list(enumerate(reconstruct_matrix))): + for row, deps in reversed(list(enumerate(self.assignments))): if len(deps) == 0: res.append(expr_all[row]) continue - new_sym = sym.Symbol(sac.assign_unique("compress_temp", expr_all[row])) - for k, v in deps: - expr_all[k] += new_sym * v + if sac is not None: + new_sym = sym.Symbol(sac.assign_unique("compress_temp", expr_all[row])) + for k, v in deps: + expr_all[k] += new_sym * v + else: + for k, v in deps: + expr_all[k] += sym.sympify(expr_all[row] * v).expand(deep=False) res.reverse() return res @@ -261,7 +289,6 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): """ .. automethod:: __init__ .. automethod:: get_pde - .. automethod:: get_reduced_coeffs """ init_arg_names = ("order", "dim", "max_mi") @@ -279,19 +306,18 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): def get_full_kernel_derivatives_from_stored(self, stored_kernel_derivatives, rscale, sac=None): - reconstruct_matrix = self.get_coefficient_matrix(rscale) - return _fast_spmv(reconstruct_matrix, stored_kernel_derivatives, sac) + projection_matrix = self.get_projection_matrix(rscale) + return projection_matrix.matvec(stored_kernel_derivatives, sac) def get_stored_mpole_coefficients_from_full(self, full_mpole_coefficients, rscale, sac=None): - # = M^T x, where M = coeff matrix - reconstruct_matrix = self.get_coefficient_matrix(rscale) - return _fast_spmv(reconstruct_matrix, full_mpole_coefficients, sac, - transpose=True) + # = M^T x, where M = projection matrix + projection_matrix = self.get_projection_matrix(rscale) + return projection_matrix.transpose_matvec(full_mpole_coefficients, sac) @property def stored_identifiers(self): - stored_identifiers, coeff_matrix = self.get_stored_ids_and_coeff_mat() + stored_identifiers, _ = self.get_stored_ids_and_unscaled_projection_matrix() return stored_identifiers def get_pde(self): @@ -304,7 +330,7 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): raise NotImplementedError @memoize_method - def get_stored_ids_and_coeff_mat(self): + def get_stored_ids_and_unscaled_projection_matrix(self): from six import iteritems from pytools import ProcessLogger @@ -319,10 +345,9 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): if ident not in coeff_ident_enumerate_dict: # Order of the expansion is less than the order of the PDE. # In that case, the compression matrix is the identity matrix - reconstruct_matrix = [] - for i in range(len(mis)): - reconstruct_matrix.append([]) - return mis, reconstruct_matrix + # and there's nothing to project + projection_matrix_assignments = [list() for i in range(len(mis))] + return mis, CSEMatVec(projection_matrix_assignments) max_mi_idx = max(coeff_ident_enumerate_dict[ident] for ident in pde_dict.keys()) @@ -338,9 +363,9 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): stored_identifiers = [mi for mi in mis if is_stored(mi)] - reconstruct_matrix = [] + projection_matrix_assignments = [] for i, mi in enumerate(mis): - reconstruct_matrix.append([]) + projection_matrix_assignments.append([]) if is_stored(mi): continue diff = [mi[d] - max_mi[d] for d in range(self.dim)] @@ -350,7 +375,7 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): continue # PDE might not have max_mi_coeff = -1, divide by -max_mi_coeff # to get a relation of the form, u_zz = - u_xx - u_yy for Laplace 3D. - reconstruct_matrix[i].append((j, coeff*max_mi_mult)) + projection_matrix_assignments[i].append((j, coeff*max_mi_mult)) plog.done() @@ -358,10 +383,10 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): .format(orig=len(self.get_full_coefficient_identifiers()), red=len(stored_identifiers))) - return stored_identifiers, reconstruct_matrix + return stored_identifiers, CSEMatVec(projection_matrix_assignments) @memoize_method - def get_coefficient_matrix(self, rscale): + def get_projection_matrix(self, rscale): """ Return a matrix that expresses every derivative in terms of a set of "stored" derivatives. @@ -379,14 +404,16 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): u_zz| ... -1 -1 ... ^ rows = one for every derivative + + the projection matrix `M` is the transpose of the coefficient matrix """ - stored_identifiers, reconstruct_matrix = \ - self.get_stored_ids_and_coeff_mat() + _, projection_matrix = \ + self.get_stored_ids_and_unscaled_projection_matrix() full_coeffs = self.get_full_coefficient_identifiers() - reconstruct_matrix_with_rscale = [] - for row, deps in enumerate(reconstruct_matrix): + projection_with_rscale = [] + for row, deps in enumerate(projection_matrix.assignments): # For eg: (u_xxx / rscale**3) = (u_yy / rscale**2) * coeff1 + # (u_xx / rscale**2) * coeff2 # is converted to u_xxx = u_yy * (rscale * coeff1) + @@ -397,9 +424,9 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): diff = row_rscale - sum(full_coeffs[k]) mult = rscale**diff deps_with_rscale.append((k, coeff * mult)) - reconstruct_matrix_with_rscale.append(deps_with_rscale) + projection_with_rscale.append(deps_with_rscale) - return reconstruct_matrix_with_rscale + return CSEMatVec(projection_with_rscale) class LaplaceExpansionTermsWrangler(LinearPDEBasedExpansionTermsWrangler): diff --git a/sumpy/expansion/local.py b/sumpy/expansion/local.py index 41e72236d4b01b7ed165a258e6108e1183efdac7..f5822332694113bbd722bf1dddecaab9a607bb04 100644 --- a/sumpy/expansion/local.py +++ b/sumpy/expansion/local.py @@ -131,7 +131,7 @@ class VolumeTaylorLocalExpansionBase(LocalExpansionBase): evaluated_coeffs = ( self.expansion_terms_wrangler.get_full_kernel_derivatives_from_stored( - coeffs, rscale)) + coeffs, rscale, sac=sac)) bvec = [b*rscale**-1 for b in bvec] mi_to_index = dict((mi, i) for i, mi in @@ -213,7 +213,7 @@ class VolumeTaylorLocalExpansionBase(LocalExpansionBase): return local_sum[0] def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale): + dvec, tgt_rscale, sac=None): logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, @@ -282,7 +282,7 @@ class VolumeTaylorLocalExpansionBase(LocalExpansionBase): # Compress the embedded coefficient set stored_coeffs = tgtplusderiv_exp_terms_wrangler \ .get_stored_mpole_coefficients_from_full( - embedded_coeffs, src_rscale) + embedded_coeffs, src_rscale, sac=sac) # Sum the embedded coefficient set for tgtplusderiv_coeff_id, coeff in zip(tgtplusderiv_coeff_ids, @@ -313,7 +313,7 @@ class VolumeTaylorLocalExpansionBase(LocalExpansionBase): # 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) + 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) @@ -412,7 +412,7 @@ class _FourierBesselLocalExpansion(LocalExpansionBase): for c in self.get_coefficient_identifiers()) def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale): + dvec, tgt_rscale, sac=None): from sumpy.symbolic import sym_real_norm_2 if not self.use_rscale: diff --git a/sumpy/expansion/multipole.py b/sumpy/expansion/multipole.py index 3328286e1b7117f0c083f566166faa05f864817a..7ad993733c9bfaee0d05d726b8d005e62409f85b 100644 --- a/sumpy/expansion/multipole.py +++ b/sumpy/expansion/multipole.py @@ -145,7 +145,7 @@ class VolumeTaylorMultipoleExpansionBase(MultipoleExpansionBase): return MiDerivativeTaker(self.kernel.get_expression(bvec), bvec) def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_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" @@ -234,7 +234,7 @@ class VolumeTaylorMultipoleExpansionBase(MultipoleExpansionBase): logger.info("building translation operator: done") return ( self.expansion_terms_wrangler.get_stored_mpole_coefficients_from_full( - result, tgt_rscale)) + result, tgt_rscale, sac=sac)) class VolumeTaylorMultipoleExpansion( @@ -326,7 +326,7 @@ class _HankelBased2DMultipoleExpansion(MultipoleExpansionBase): for c in self.get_coefficient_identifiers()) def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale): + dvec, tgt_rscale, sac=None): if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to %s" % (type(src_expansion).__name__,