From 07479232e4fe851dc39fa408896970eef622cd71 Mon Sep 17 00:00:00 2001 From: Isuru Fernando <isuruf@gmail.com> Date: Sun, 21 Apr 2019 17:59:15 -0500 Subject: [PATCH] reconstruction matrix --- sumpy/expansion/__init__.py | 96 +++++++++++++++++++++++++++++++++---- test/test_kernels.py | 24 +++++----- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py index ddf7b899..02ba9fec 100644 --- a/sumpy/expansion/__init__.py +++ b/sumpy/expansion/__init__.py @@ -266,6 +266,19 @@ def _spmv(spmat, x, sparse_vectors): # }}} +def _reconstruct(reconstruct_matrix, stored): + res = [0] * len(reconstruct_matrix) + stored_idx = 0 + for row, deps in reconstruct_matrix: + if len(deps) == 0: + res[row] = stored[stored_idx] + stored_idx += 1 + continue + for k, v in deps.items(): + res[row] += res[k] * v + return res + + class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): """ .. automethod:: __init__ @@ -288,15 +301,18 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): def get_full_kernel_derivatives_from_stored(self, stored_kernel_derivatives, rscale): - coeff_matrix = self.get_coefficient_matrix(rscale) - return _spmv(coeff_matrix, stored_kernel_derivatives, + coeff_matrix, reconstruct_matrix, use_reconstruct = \ + self.get_coefficient_matrix(rscale) + if not use_reconstruct: + return _spmv(coeff_matrix, stored_kernel_derivatives, sparse_vectors=False) + return _reconstruct(reconstruct_matrix, stored_kernel_derivatives) def get_stored_mpole_coefficients_from_full(self, full_mpole_coefficients, rscale): # = M^T x, where M = coeff matrix - coeff_matrix = self.get_coefficient_matrix(rscale) + coeff_matrix, _, _ = self.get_coefficient_matrix(rscale) result = [0] * len(self.stored_identifiers) for row, coeff in enumerate(full_mpole_coefficients): for col, val in coeff_matrix[row]: @@ -305,7 +321,7 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): @property def stored_identifiers(self): - stored_identifiers, coeff_matrix = self._get_stored_ids_and_coeff_mat() + stored_identifiers, coeff_matrix, _ = self.get_stored_ids_and_coeff_mat() return stored_identifiers @memoize_method @@ -328,11 +344,13 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): ^ rows = one for every derivative """ - stored_identifiers, coeff_matrix = self._get_stored_ids_and_coeff_mat() + stored_identifiers, coeff_matrix, reconstruct_matrix = \ + self.get_stored_ids_and_coeff_mat() full_coeffs = self.get_full_coefficient_identifiers() matrix_rows = [] _, deriv_multiplier, _, _ = self._get_pdes() + count_nonzero_coeff = 0 for irow, row in six.iteritems(coeff_matrix): # For eg: (u_xxx / rscale**3) = (u_yy / rscale**2) * coeff1 + # (u_xx / rscale**2) * coeff2 @@ -344,9 +362,37 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): diff = row_rscale - sum(stored_identifiers[icol]) mult = (rscale*deriv_multiplier)**diff matrix_row.append((icol, coeff * mult)) + count_nonzero_coeff += len(row) matrix_rows.append((irow, matrix_row)) - return defaultdict(list, matrix_rows) + if reconstruct_matrix is None: + return defaultdict(list, matrix_rows), None, False + + reconstruct_matrix_with_rscale = [] + count_nonzero_reconstruct = 0 + for row, deps in reconstruct_matrix: + # 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]) + matrix_row = [] + deps_with_rscale = {} + for k, coeff in deps.items(): + diff = row_rscale - sum(full_coeffs[k]) + mult = (rscale*deriv_multiplier)**diff + deps_with_rscale[k] = coeff * mult + count_nonzero_reconstruct += len(deps) + reconstruct_matrix_with_rscale.append((row, deps_with_rscale)) + + use_reconstruct = count_nonzero_reconstruct < count_nonzero_coeff + + return defaultdict(list, matrix_rows), reconstruct_matrix_with_rscale, \ + use_reconstruct + + @memoize_method + def get_stored_ids_and_coeff_mat(self): + return self._get_stored_ids_and_coeff_mat()[:3] @memoize_method def _get_stored_ids_and_coeff_mat(self): @@ -357,7 +403,7 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): stored_identifiers = [] from pytools import ProcessLogger - plog = ProcessLogger(logger, "compute recurrence for Taylor coefficients") + plog = ProcessLogger(logger, "compute PDE for Taylor coefficients") pdes, _, iexpr, nexpr = self._get_pdes() pde_mat = [] @@ -377,6 +423,7 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): else: pde_mat.append(eq) + reconstruct_matrix = None if len(pde_mat) > 0: r""" Find a matrix :math:`s` such that :math:`K = S^T K_{[r]}` @@ -404,15 +451,18 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): idx_all_exprs = self.get_reduced_coeffs(n) s = solve_symbolic(n.T[:, idx_all_exprs], n.T) - # Filter out coefficients belonging to this expression indices = list(filter(lambda x: x < offset, idx_all_exprs)) s = s[:len(indices), :offset] stored_identifiers = [mis[i] for i in indices] + if nexpr == 1: + reconstruct_matrix = self._get_reconstruct_matrix(pde_mat, + indices) else: s = np.eye(len(mis)) stored_identifiers = mis + idx_all_exprs = list(range(len(mis))) # coeff_matrix is a dictionary of lists. Each key in the dictionary # is a row number and the values are pairs of column number and value. @@ -428,7 +478,35 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): .format(orig=len(self.get_full_coefficient_identifiers()), red=len(stored_identifiers))) - return stored_identifiers, coeff_matrix + return stored_identifiers, coeff_matrix, reconstruct_matrix, pde_mat, \ + idx_all_exprs + + def _get_reconstruct_matrix(self, pde_mat, stored): + constructed = [False]*len(pde_mat[0]) + reconstruct = [] + for i in stored: + reconstruct.append((i, dict())) + constructed[i] = True + found = True + while found: + found = False + for j in range(len(pde_mat)): + deps = np.nonzero(pde_mat[j])[0] + c = [not constructed[d] for d in deps] + if np.count_nonzero(c) == 1: + new_index = deps[np.nonzero(c)[0]][0] + recurrence = {} + for d in deps: + if not constructed[d]: + assert d == new_index + continue + recurrence[d] = -pde_mat[j][d]/pde_mat[j][new_index] + reconstruct.append((new_index, recurrence)) + constructed[new_index] = True + found = True + if np.all(constructed): + return reconstruct + return None def get_pdes(self): r""" diff --git a/test/test_kernels.py b/test/test_kernels.py index 04ab51ea..313fdd63 100644 --- a/test/test_kernels.py +++ b/test/test_kernels.py @@ -22,8 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from six.moves import range - import numpy as np import numpy.linalg as la import sys @@ -186,7 +184,7 @@ def test_p2e2p(ctx_getter, base_knl, expn_class, order, with_source_derivative): else: h_values = [1/2, 1/3, 1/5] - center = np.array([2, 1], np.float64) + center = np.array([2, 1, 0][:knl.dim], np.float64) sources = (0.7*(-0.5+np.random.rand(knl.dim, nsources).astype(np.float64)) + center[:, np.newaxis]) @@ -206,15 +204,15 @@ def test_p2e2p(ctx_getter, base_knl, expn_class, order, with_source_derivative): for h in h_values: if issubclass(expn_class, LocalExpansionBase): - loc_center = np.array([5.5, 0.0]) + center + loc_center = np.array([5.5, 0.0, 0.0][:knl.dim]) + center centers = np.array(loc_center, dtype=np.float64).reshape(knl.dim, 1) fp = FieldPlotter(loc_center, extent=h, npoints=res) else: - eval_center = np.array([1/h, 0.0]) + center + eval_center = np.array([1/h, 0.0, 0.0][:knl.dim]) + center fp = FieldPlotter(eval_center, extent=0.1, npoints=res) - centers = ( - np.array([0.0, 0.0], dtype=np.float64).reshape(knl.dim, 1) - + center[:, np.newaxis]) + centers = (np.array([0.0, 0.0, 0.0][:knl.dim], + dtype=np.float64).reshape(knl.dim, 1) + + center[:, np.newaxis]) targets = fp.points @@ -375,7 +373,7 @@ def test_translations(ctx_getter, knl, local_expn_class, mpole_expn_class): extra_kwargs["mu"] = 0.05 # Just to make sure things also work away from the origin - origin = np.array([2, 1], np.float64) + origin = np.array([2, 1, 0][:knl.dim], np.float64) sources = (0.7*(-0.5+np.random.rand(knl.dim, nsources).astype(np.float64)) + origin[:, np.newaxis]) strengths = np.ones(nsources, dtype=np.float64) * (1/nsources) @@ -387,18 +385,18 @@ def test_translations(ctx_getter, knl, local_expn_class, mpole_expn_class): from sumpy.visualization import FieldPlotter - eval_offset = np.array([5.5, 0.0]) + eval_offset = np.array([5.5, 0.0, 0][:knl.dim]) centers = (np.array( [ # box 0: particles, first mpole here - [0, 0], + [0, 0, 0][:knl.dim], # box 1: second mpole here - np.array([-0.2, 0.1], np.float64), + np.array([-0.2, 0.1, 0][:knl.dim], np.float64), # box 2: first local here - eval_offset + np.array([0.3, -0.2], np.float64), + eval_offset + np.array([0.3, -0.2, 0][:knl.dim], np.float64), # box 3: second local and eval here eval_offset -- GitLab