From 8d659e84f5cd45d4502e4a7e1bf87114960f09f7 Mon Sep 17 00:00:00 2001 From: Matt Wala <wala1@illinois.edu> Date: Fri, 6 Feb 2015 21:00:41 -0600 Subject: [PATCH] Get the FMM working with Bessel expansions on the 2D Helmholtz kernel. - Add a H2DMultipoleExpansion class. - Implement L2L, M2L, M2M operators. - Teach loopy about casting to complex numbers (to handle a corner case where some multipole coefficients may look real valued). - Update the tests. --- sumpy/codegen.py | 20 +++++++++++ sumpy/expansion/local.py | 68 +++++++++++++++++++++++++----------- sumpy/expansion/multipole.py | 61 +++++++++++++++++++++++++++++++- sumpy/kernel.py | 5 +-- test/test_fmm.py | 25 +++++++------ test/test_kernels.py | 20 ++++++----- 6 files changed, 156 insertions(+), 43 deletions(-) diff --git a/sumpy/codegen.py b/sumpy/codegen.py index 29a2543e..1c91e7bf 100644 --- a/sumpy/codegen.py +++ b/sumpy/codegen.py @@ -65,6 +65,26 @@ class SympyToPymbolicMapper(SympyToPymbolicMapperBase): # }}} +# {{{ complex handling + +def complex_mangler(identifier, arg_dtypes): + """A function "mangler" to make casting to complex values + digestible for :mod:`loopy`. + + See argument *function_manglers* to :func:`loopy.make_kernel`. + """ + + if identifier == "complex" and arg_dtypes == (np.dtype(np.float64),): + return (np.dtype(np.complex128), "cdouble_fromreal", + (np.dtype(np.float64),)) + elif identifier == "complex" and arg_dtypes == (np.dtype(np.complex128),): + return (np.dtype(np.complex128), "cdouble_cast", + (np.dtype(np.complex128),)) + else: + return None + +# }}} + # {{{ bessel handling diff --git a/sumpy/expansion/local.py b/sumpy/expansion/local.py index 699fa3f4..bff7acee 100644 --- a/sumpy/expansion/local.py +++ b/sumpy/expansion/local.py @@ -123,34 +123,60 @@ class H2DLocalExpansion(LocalExpansionBase): return range(-self.order, self.order+1) def coefficients_from_source(self, avec, bvec): - hankel_1 = sp.Function("hankel_1") - center_source_angle = sp.atan2(-avec[1], -avec[0]) - from sumpy.symbolic import sympy_real_norm_2 - u = sympy_real_norm_2(avec) - - e_i_csangle = sp.exp(sp.I*center_source_angle) - return [ - self.kernel.postprocess_at_source( - hankel_1(i, sp.Symbol("k")*u)*e_i_csangle**i, - avec) - for i in self.get_coefficient_identifiers()] + hankel_1 = sp.Function("hankel_1") + # The coordinates are negated since avec points from source to center. + source_angle_rel_center = sp.atan2(-avec[1], -avec[0]) + avec_len = sympy_real_norm_2(avec) + return [self.kernel.postprocess_at_source( + hankel_1(l, sp.Symbol("k") * avec_len) + * sp.exp(sp.I * l * source_angle_rel_center), avec) + for l in self.get_coefficient_identifiers()] def evaluate(self, coeffs, bvec): + from sumpy.symbolic import sympy_real_norm_2 bessel_j = sp.Function("bessel_j") + bvec_len = sympy_real_norm_2(bvec) + target_angle_rel_center = sp.atan2(bvec[1], bvec[0]) + return sum(coeffs[self.get_storage_index(l)] + * self.kernel.postprocess_at_target( + bessel_j(l, sp.Symbol("k") * bvec_len) + * sp.exp(sp.I * l * -target_angle_rel_center), bvec) + for l in self.get_coefficient_identifiers()) + def translate_from(self, src_expansion, src_coeff_exprs, dvec): from sumpy.symbolic import sympy_real_norm_2 - v = sympy_real_norm_2(bvec) - - center_target_angle = sp.atan2(bvec[1], bvec[0]) - e_i_ctangle = sp.exp(-sp.I*center_target_angle) - return sum( - coeffs[self.get_storage_index(i)] - * self.kernel.postprocess_at_target( - bessel_j(i, sp.Symbol("k")*v) - * e_i_ctangle**i, bvec) - for i in self.get_coefficient_identifiers()) + if isinstance(src_expansion, H2DLocalExpansion): + dvec_len = sympy_real_norm_2(dvec) + bessel_j = sp.Function("bessel_j") + new_center_angle_rel_old_center = sp.atan2(dvec[1], dvec[0]) + translated_coeffs = [] + for l in self.get_coefficient_identifiers(): + translated_coeffs.append( + sum(src_coeff_exprs[src_expansion.get_storage_index(m)] + * bessel_j(m - l, sp.Symbol("k") * dvec_len) + * sp.exp(sp.I * (m - l) * -new_center_angle_rel_old_center) + for m in src_expansion.get_coefficient_identifiers())) + return translated_coeffs + + from sumpy.expansion.multipole import H2DMultipoleExpansion + if isinstance(src_expansion, H2DMultipoleExpansion): + dvec_len = sympy_real_norm_2(dvec) + hankel_1 = sp.Function("hankel_1") + new_center_angle_rel_old_center = sp.atan2(dvec[1], dvec[0]) + translated_coeffs = [] + for l in self.get_coefficient_identifiers(): + translated_coeffs.append( + sum((-1) ** l * hankel_1(m + l, sp.Symbol("k") * dvec_len) + * sp.exp(sp.I * (m + l) * new_center_angle_rel_old_center) + * src_coeff_exprs[src_expansion.get_storage_index(m)] + for m in src_expansion.get_coefficient_identifiers())) + return translated_coeffs + + raise RuntimeError("do not know how to translate %s to " + "local 2D Hankel expansion" + % type(src_expansion).__name__) # }}} diff --git a/sumpy/expansion/multipole.py b/sumpy/expansion/multipole.py index c89e4c8b..c572e115 100644 --- a/sumpy/expansion/multipole.py +++ b/sumpy/expansion/multipole.py @@ -87,7 +87,7 @@ class VolumeTaylorMultipoleExpansion( if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to " "Taylor multipole expansion" - % type(src_expansion).__name) + % type(src_expansion).__name__) logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, @@ -129,4 +129,63 @@ class VolumeTaylorMultipoleExpansion( # }}} + +# {{{ 2D J-expansion + +class H2DMultipoleExpansion(MultipoleExpansionBase): + def __init__(self, kernel, order): + from sumpy.kernel import HelmholtzKernel + assert (isinstance(kernel.get_base_kernel(), HelmholtzKernel) + and kernel.dim == 2) + + MultipoleExpansionBase.__init__(self, kernel, order) + + def get_storage_index(self, k): + return self.order+k + + def get_coefficient_identifiers(self): + return range(-self.order, self.order+1) + + def coefficients_from_source(self, avec, bvec): + from sumpy.symbolic import sympy_real_norm_2 + bessel_j = sp.Function("bessel_j") + avec_len = sympy_real_norm_2(avec) + # The coordinates are negated since avec points from source to center. + source_angle_rel_center = sp.atan2(-avec[1], -avec[0]) + return [sp.Function("complex")(self.kernel.postprocess_at_source( + bessel_j(l, sp.Symbol("k") * avec_len) * + sp.exp(sp.I * l * -source_angle_rel_center), avec)) + for l in self.get_coefficient_identifiers()] + + def evaluate(self, coeffs, bvec): + from sumpy.symbolic import sympy_real_norm_2 + hankel_1 = sp.Function("hankel_1") + bvec_len = sympy_real_norm_2(bvec) + target_angle_rel_center = sp.atan2(bvec[1], bvec[0]) + return sum(coeffs[self.get_storage_index(l)] + * self.kernel.postprocess_at_target( + hankel_1(l, sp.Symbol("k") * bvec_len) + * sp.exp(sp.I * l * target_angle_rel_center), bvec) + for l in self.get_coefficient_identifiers()) + + def translate_from(self, src_expansion, src_coeff_exprs, dvec): + if not isinstance(src_expansion, type(self)): + raise RuntimeError("do not know how to translate %s to " + "multipole 2D Hankel expansion" + % type(src_expansion).__name__) + from sumpy.symbolic import sympy_real_norm_2 + dvec_len = sympy_real_norm_2(dvec) + bessel_j = sp.Function("bessel_j") + new_center_angle_rel_old_center = sp.atan2(dvec[1], dvec[0]) + translated_coeffs = [] + for l in self.get_coefficient_identifiers(): + translated_coeffs.append( + sum(src_coeff_exprs[src_expansion.get_storage_index(m)] + * bessel_j(m - l, sp.Symbol("k") * dvec_len) + * sp.exp(sp.I * (m - l) * new_center_angle_rel_old_center) + for m in src_expansion.get_coefficient_identifiers())) + return translated_coeffs + +# }}} + # vim: fdm=marker diff --git a/sumpy/kernel.py b/sumpy/kernel.py index 371a3069..4fbbaedc 100644 --- a/sumpy/kernel.py +++ b/sumpy/kernel.py @@ -366,9 +366,10 @@ class HelmholtzKernel(ExpressionKernel): return "HelmKnl(%s)" % (self.helmholtz_k_name) def prepare_loopy_kernel(self, loopy_knl): - from sumpy.codegen import bessel_preamble_generator, bessel_mangler + from sumpy.codegen import (bessel_preamble_generator, bessel_mangler, + complex_mangler) loopy_knl = lp.register_function_manglers(loopy_knl, - [bessel_mangler]) + [bessel_mangler, complex_mangler]) loopy_knl = lp.register_preamble_generators(loopy_knl, [bessel_preamble_generator]) diff --git a/test/test_fmm.py b/test/test_fmm.py index 8ba2a34b..0726ca76 100644 --- a/test/test_fmm.py +++ b/test/test_fmm.py @@ -29,6 +29,11 @@ import pyopencl as cl from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) from sumpy.kernel import LaplaceKernel, HelmholtzKernel +from sumpy.expansion.multipole import ( + VolumeTaylorMultipoleExpansion, H2DMultipoleExpansion) +from sumpy.expansion.local import ( + VolumeTaylorLocalExpansion, H2DLocalExpansion) + import pytest import logging @@ -43,13 +48,14 @@ else: faulthandler.enable() -@pytest.mark.parametrize("knl", [ - LaplaceKernel(2), - LaplaceKernel(3), - HelmholtzKernel(2), - HelmholtzKernel(3), +@pytest.mark.parametrize("knl, local_expn_class, mpole_expn_class", [ + (LaplaceKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + (LaplaceKernel(3), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + (HelmholtzKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion), + (HelmholtzKernel(3), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion) ]) -def test_sumpy_fmm(ctx_getter, knl): +def test_sumpy_fmm(ctx_getter, knl, local_expn_class, mpole_expn_class): logging.basicConfig(level=logging.INFO) ctx = ctx_getter() @@ -118,9 +124,6 @@ def test_sumpy_fmm(ctx_getter, knl): logger.info("computing direct (reference) result") - from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion - from sumpy.expansion.local import VolumeTaylorLocalExpansion - from pytools.convergence import PConvergenceVerifier pconv_verifier = PConvergenceVerifier() @@ -136,8 +139,8 @@ def test_sumpy_fmm(ctx_getter, knl): order_values = [1, 2] for order in order_values: - mpole_expn = VolumeTaylorMultipoleExpansion(knl, order) - local_expn = VolumeTaylorLocalExpansion(knl, order) + mpole_expn = mpole_expn_class(knl, order) + local_expn = local_expn_class(knl, order) out_kernels = [knl] from sumpy.fmm import SumpyExpansionWranglerCodeContainer diff --git a/test/test_kernels.py b/test/test_kernels.py index ec1ab2d3..44861576 100644 --- a/test/test_kernels.py +++ b/test/test_kernels.py @@ -31,7 +31,8 @@ import pyopencl as cl from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion +from sumpy.expansion.multipole import ( + VolumeTaylorMultipoleExpansion, H2DMultipoleExpansion) from sumpy.expansion.local import VolumeTaylorLocalExpansion, H2DLocalExpansion from sumpy.kernel import (LaplaceKernel, HelmholtzKernel, AxisTargetDerivative, DirectionalSourceDerivative) @@ -95,6 +96,7 @@ def test_p2p(ctx_getter): (HelmholtzKernel(2), VolumeTaylorMultipoleExpansion), (HelmholtzKernel(2), VolumeTaylorLocalExpansion), (HelmholtzKernel(2), H2DLocalExpansion), + (HelmholtzKernel(2), H2DMultipoleExpansion) ]) @pytest.mark.parametrize("with_source_derivative", [ False, @@ -279,11 +281,12 @@ def test_p2e2p(ctx_getter, knl, expn_class, order, with_source_derivative): assert eoc_rec_grad_x.order_estimate() > tgt_order_grad - grad_slack -@pytest.mark.parametrize("knl", [ - LaplaceKernel(2), - HelmholtzKernel(2) +@pytest.mark.parametrize("knl, local_expn_class, mpole_expn_class", [ + (LaplaceKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + (HelmholtzKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion) ]) -def test_translations(ctx_getter, knl): +def test_translations(ctx_getter, knl, local_expn_class, mpole_expn_class): logging.basicConfig(level=logging.INFO) from sympy.core.cache import clear_cache @@ -336,7 +339,8 @@ def test_translations(ctx_getter, knl): del eval_offset - if isinstance(knl, HelmholtzKernel): + if isinstance(knl, HelmholtzKernel) and \ + isinstance(local_expn_class, VolumeTaylorLocalExpansion): # FIXME: Embarrassing--but we run out of memory for higher orders. orders = [2, 3] else: @@ -366,8 +370,8 @@ def test_translations(ctx_getter, knl): return pot for order in orders: - m_expn = VolumeTaylorMultipoleExpansion(knl, order=order) - l_expn = VolumeTaylorLocalExpansion(knl, order=order) + m_expn = mpole_expn_class(knl, order=order) + l_expn = local_expn_class(knl, order=order) from sumpy import P2EFromSingleBox, E2PFromSingleBox, P2P, E2EFromCSR p2m = P2EFromSingleBox(ctx, m_expn) -- GitLab