From 90305494c7d8154d146cc55d121fa2ee25970a81 Mon Sep 17 00:00:00 2001 From: Isuru Fernando <isuruf@gmail.com> Date: Wed, 8 Jul 2020 21:10:25 -0500 Subject: [PATCH] Generalized CSEMatVec --- sumpy/expansion/__init__.py | 95 +++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py index d1ad0644..971d0ed9 100644 --- a/sumpy/expansion/__init__.py +++ b/sumpy/expansion/__init__.py @@ -227,7 +227,7 @@ class FullExpansionTermsWrangler(ExpansionTermsWrangler): # {{{ sparse matrix-vector multiplication -class CSEMatVec(object): +class CSEMatVecOperator(object): """ A class to facilitate a fast matrix vector multiplication with common subexpression eliminated. In compressed Taylor @@ -236,19 +236,24 @@ class CSEMatVec(object): .. attribute:: assignments - A list of lists indicating a linear combination of previously - calculated rows. If an element of the `assignments` is empty, - then that row is taken from the first unused row from the - vector. Each element is a list of tuples where first argument - is a previous row number and the second argument is the weight - in the linear combination. + A object of type List[Tuple[List[Tuple[int, Any]], List[Tuple[int, Any]]]]. + Each element in the list represents a row of the Matrix using a tuple + of linear combinations. In the tuple, the first argument is a list of tuples + representing (index of input vector, coeff) and the second argument of a + list of tuples representing (index of output vector, coeff) Number of rows in the matrix represented is equal to the length of the `assignments` list. + + .. attribute:: shape + + Shape of the matrix as a tuple. """ - def __init__(self, assignments): + def __init__(self, assignments, shape): self.assignments = assignments + self.shape = shape + assert len(self.assignments) == shape[0] def matvec(self, vec, wrap_intermediate=lambda x: x): """ @@ -258,31 +263,28 @@ class CSEMatVec(object): If not given, the number of operations might grow in the final expressions in the vector resulting in an expensive matvec. """ - res = [0] * len(self.assignments) - stored_idx = 0 - 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 - res[row] = wrap_intermediate(res[row]) + assert len(vec) == self.shape[1] + res = [] + for input_vec_list, output_vec_list in self.assignments: + value = 0 + for input_index, coeff in input_vec_list: + value += vec[input_index] * coeff + for output_index, coeff in output_vec_list: + value += res[output_index] * coeff + res.append(wrap_intermediate(value)) return res def transpose_matvec(self, vec, wrap_intermediate=lambda x: x): - res = [] + assert len(vec) == self.shape[0] + res = [0]*self.shape[1] expr_all = list(vec) - for row, deps in reversed(list(enumerate(self.assignments))): - if len(deps) == 0: - res.append(expr_all[row]) - continue - - new_sym = wrap_intermediate(expr_all[row]) - for k, v in deps: - expr_all[k] += new_sym * v - - res.reverse() + for i, (input_vec_list, output_vec_list) in \ + reversed(list(enumerate(self.assignments))): + for output_index, coeff in output_vec_list: + expr_all[output_index] += expr_all[i] * coeff + expr_all[output_index] = wrap_intermediate(expr_all[output_index]) + for input_index, coeff in input_vec_list: + res[input_index] += expr_all[i] * coeff return res # }}} @@ -360,8 +362,10 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): # Order of the expansion is less than the order of the PDE. # In that case, the compression matrix is the identity matrix # and there's nothing to project - projection_matrix_assignments = [list() for i in range(len(mis))] - return mis, CSEMatVec(projection_matrix_assignments) + projection_matrix_assignments = \ + [([(i, 1)], []) for i in range(len(mis))] + shape = (len(mis), len(mis)) + return mis, CSEMatVecOperator(projection_matrix_assignments, shape) # Calculate the multi-index that appears last in in the PDE in # reverse degree lexicographic order. @@ -377,19 +381,22 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): """ return any(mi[d] < max_mi[d] for d in range(self.dim)) - stored_identifiers = [mi for mi in mis if is_stored(mi)] + stored_identifiers = [] projection_matrix_assignments = [] for i, mi in enumerate(mis): - projection_matrix_assignments.append([]) # If the multi-index is to be stored, keep the projection matrix # entry empty if is_stored(mi): + idx = len(stored_identifiers) + stored_identifiers.append(mi) + projection_matrix_assignments.append(([(idx, 1)], [])) continue diff = [mi[d] - max_mi[d] for d in range(self.dim)] # eg: u_xx + u_yy + u_zz is represented as # [((2, 0, 0), 1), ((0, 2, 0), 1), ((0, 0, 2), 1)] + assignment = [] for other_mi, coeff in iteritems(pde_dict): j = coeff_ident_enumerate_dict[add_mi(other_mi, diff)] if i == j: @@ -397,7 +404,8 @@ 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. - projection_matrix_assignments[i].append((j, coeff*max_mi_mult)) + assignment.append((j, coeff*max_mi_mult)) + projection_matrix_assignments.append(([], assignment)) plog.done() @@ -405,12 +413,14 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): .format(orig=len(self.get_full_coefficient_identifiers()), red=len(stored_identifiers))) - return stored_identifiers, CSEMatVec(projection_matrix_assignments) + shape = (len(mis), len(stored_identifiers)) + op = CSEMatVecOperator(projection_matrix_assignments, shape) + return stored_identifiers, op @memoize_method def get_projection_matrix(self, rscale): """ - Return a :class:`CSEMatVec` class which exposes a matrix vector + Return a :class:`CSEMatVecOperator` object which exposes a matrix vector multiplication operator for the projection matrix that expresses every derivative in terms of a set of "stored" derivatives. @@ -436,20 +446,21 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): full_coeffs = self.get_full_coefficient_identifiers() projection_with_rscale = [] - for row, deps in enumerate(projection_matrix.assignments): + for row, assignment 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) + # u_xx * (rscale * coeff2) row_rscale = sum(full_coeffs[row]) - deps_with_rscale = [] - for k, coeff in deps: + assignment_with_rscale = [assignment[0].copy(), []] + for k, coeff in assignment[1]: diff = row_rscale - sum(full_coeffs[k]) mult = rscale**diff - deps_with_rscale.append((k, coeff * mult)) - projection_with_rscale.append(deps_with_rscale) + assignment_with_rscale[1].append((k, coeff * mult)) + projection_with_rscale.append(tuple(assignment_with_rscale)) - return CSEMatVec(projection_with_rscale) + shape = projection_matrix.shape + return CSEMatVecOperator(projection_with_rscale, shape) class LaplaceExpansionTermsWrangler(LinearPDEBasedExpansionTermsWrangler): -- GitLab