From 058c914985fa8ad8c8927dc4fbeb1394a352ed6b Mon Sep 17 00:00:00 2001 From: Isuru Fernando <isuruf@gmail.com> Date: Fri, 26 Jan 2018 14:17:22 -0600 Subject: [PATCH] NewLinearRecurrenceBasedDerivativeWrangler --- sumpy/expansion/__init__.py | 89 ++++++++++++++++++++++++++++--------- sumpy/tools.py | 75 +++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 20 deletions(-) diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py index 5432a54e..e66004b2 100644 --- a/sumpy/expansion/__init__.py +++ b/sumpy/expansion/__init__.py @@ -368,32 +368,81 @@ class LinearRecurrenceBasedDerivativeWrangler(DerivativeWrangler): return LinearRecurrenceBasedMiDerivativeTaker(expr, var_list, self) -class LaplaceDerivativeWrangler(LinearRecurrenceBasedDerivativeWrangler): +class NewLinearRecurrenceBasedDerivativeWrangler \ + (LinearRecurrenceBasedDerivativeWrangler): - def try_get_recurrence_for_derivative(self, deriv, in_terms_of, rscale): - deriv = np.array(deriv, dtype=int) + @memoize_method + def _get_stored_ids_and_coeff_mat(self): + from scipy.linalg.interpolative import interp_decomp + from six import iteritems + from sumpy.tools import nullspace - for dim in np.where(2 <= deriv)[0]: - # Check if we can reduce this dimension in terms of the other - # dimensions. + tol = 1e-13 + stored_identifiers = [] - reduced_deriv = deriv.copy() - reduced_deriv[dim] -= 2 - coeffs = {} + import time + start_time = time.time() + logger.debug("computing recurrence for Taylor coefficients: start") - for other_dim in range(self.dim): - if other_dim == dim: - continue - needed_deriv = reduced_deriv.copy() - needed_deriv[other_dim] += 2 - needed_deriv = tuple(needed_deriv) + pde_dict = self.get_pde_dict() + P = [] + + mis = self.get_full_coefficient_identifiers() + coeff_ident_enumerate_dict = dict((tuple(mi), i) for + (i, mi) in enumerate(mis)) + + for mi in mis: + for pde_mi, coeff in iteritems(pde_dict): + diff = np.array(mi, dtype=int) - pde_mi + if (diff >= 0).all(): + eq = [0]*len(mis) + for pde_mi2, coeff2 in iteritems(pde_dict): + c = tuple(pde_mi2 + diff) + eq[coeff_ident_enumerate_dict[c]] = 1 + P.append(eq) + + P = np.array(P, dtype=np.float64) + N = nullspace(P, atol=tol) + k, idx, _ = interp_decomp(N.T, tol) + idx = idx[:k] + S = np.linalg.solve(N[idx, :].T, N.T).T + stored_identifiers = [mis[i] for i in idx] - if needed_deriv not in in_terms_of: - break + coeff_matrix = defaultdict(lambda: []) + for i in range(S.shape[0]): + for j in range(S.shape[1]): + if np.abs(S[i][j]) > tol: + coeff_matrix[i].append((j, S[i][j])) - coeffs[needed_deriv] = -1 - else: - return coeffs + logger.debug("computing recurrence for Taylor coefficients: " + "done after {dur:.2f} seconds" + .format(dur=time.time() - start_time)) + + logger.debug("number of Taylor coefficients was reduced from {orig} to {red}" + .format(orig=len(self.get_full_coefficient_identifiers()), + red=len(stored_identifiers))) + + return stored_identifiers, coeff_matrix + + @memoize_method + def get_coefficient_matrix(self, rscale): + _, coeff_matrix = self._get_stored_ids_and_coeff_mat() + return coeff_matrix + + def get_derivative_taker(self, expr, var_list): + from sumpy.tools import NewLinearRecurrenceBasedMiDerivativeTaker + return NewLinearRecurrenceBasedMiDerivativeTaker(expr, var_list, self) + + +class LaplaceDerivativeWrangler(NewLinearRecurrenceBasedDerivativeWrangler): + + def get_pde_dict(self): + pde_dict = {} + for d in range(self.dim): + mi = [0]*self.dim + mi[d] = 2 + pde_dict[tuple(mi)] = 1 + return pde_dict class HelmholtzDerivativeWrangler(LinearRecurrenceBasedDerivativeWrangler): diff --git a/sumpy/tools.py b/sumpy/tools.py index 46fa9ee9..36d76626 100644 --- a/sumpy/tools.py +++ b/sumpy/tools.py @@ -139,6 +139,37 @@ class LinearRecurrenceBasedMiDerivativeTaker(MiDerivativeTaker): return expr + +class NewLinearRecurrenceBasedMiDerivativeTaker(MiDerivativeTaker): + """ + The derivative taker for expansions that use + :class:`sumpy.expansion.LinearRecurrenceBasedDerivativeWrangler` + """ + + def __init__(self, expr, var_list, wrangler): + super(NewLinearRecurrenceBasedMiDerivativeTaker, self).__init__( + expr, var_list) + self.wrangler = wrangler + + @memoize_method + def diff(self, mi): + """ + :arg mi: a multi-index (tuple) indicating how many x/y derivatives are + to be taken. + """ + try: + expr = self.cache_by_mi[mi] + except KeyError: + closest_mi = self.get_closest_cached_mi(mi) + expr = self.cache_by_mi[closest_mi] + + for next_deriv, next_mi in ( + self.get_derivative_taking_sequence(closest_mi, mi)): + expr = expr.diff(next_deriv) + self.cache_by_mi[next_mi] = expr + + return expr + # }}} @@ -448,4 +479,48 @@ def my_syntactic_subs(expr, subst_dict): return expr +# Source: http://scipy-cookbook.readthedocs.io/items/RankNullspace.html +# Author: SciPy Developers +# License: BSD-3-Clause + +def nullspace(A, atol=1e-13, rtol=0): + """Compute an approximate basis for the nullspace of A. + + The algorithm used by this function is based on the singular value + decomposition of `A`. + + Parameters + ---------- + A : ndarray + A should be at most 2-D. A 1-D array with length k will be treated + as a 2-D with shape (1, k) + atol : float + The absolute tolerance for a zero singular value. Singular values + smaller than `atol` are considered to be zero. + rtol : float + The relative tolerance. Singular values less than rtol*smax are + considered to be zero, where smax is the largest singular value. + + If both `atol` and `rtol` are positive, the combined tolerance is the + maximum of the two; that is:: + tol = max(atol, rtol * smax) + Singular values smaller than `tol` are considered to be zero. + + Return value + ------------ + ns : ndarray + If `A` is an array with shape (m, k), then `ns` will be an array + with shape (k, n), where n is the estimated dimension of the + nullspace of `A`. The columns of `ns` are a basis for the + nullspace; each element in numpy.dot(A, ns) will be approximately + zero. + """ + from numpy.linalg import svd + A = np.atleast_2d(A) + u, s, vh = svd(A) + tol = max(atol, rtol * s[0]) + nnz = (s >= tol).sum() + ns = vh[nnz:].conj().T + return ns + # vim: fdm=marker -- GitLab