diff --git a/sumpy/codegen.py b/sumpy/codegen.py index 948512dfe884c35e0f421c4a4314796e38b0ab7b..bef7fc6bf93a171a96626f9db88c312c23167bf2 100644 --- a/sumpy/codegen.py +++ b/sumpy/codegen.py @@ -581,6 +581,28 @@ class BigIntegerKiller(CSECachingMapperMixin, IdentityMapper): # }}} +# {{{ convert 123000000j to 123000000 * 1j + +class ComplexRewriter(CSECachingMapperMixin, IdentityMapper): + + def __init__(self, float_type=np.float32): + IdentityMapper.__init__(self) + self.float_type = float_type + + def map_constant(self, expr): + """Convert complex values not within complex64 to a product for loopy + """ + if not isinstance(expr, complex): + return IdentityMapper.map_constant(self, expr) + + if complex(self.float_type(expr.imag)) == expr.imag: + return IdentityMapper.map_constant(self, expr) + + return expr.real + prim.Product((expr.imag, 1j)) + + map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + + # {{{ vector component rewriter INDEXED_VAR_RE = re.compile("^([a-zA-Z_]+)([0-9]+)$") @@ -684,6 +706,7 @@ def to_loopy_insns(assignments, vector_names=set(), pymbolic_expr_maps=[], ssg = SumSignGrouper() fck = FractionKiller() bik = BigIntegerKiller() + cmr = ComplexRewriter() def convert_expr(name, expr): logger.debug("generate expression for: %s" % name) @@ -694,6 +717,7 @@ def to_loopy_insns(assignments, vector_names=set(), pymbolic_expr_maps=[], expr = fck(expr) expr = ssg(expr) expr = bik(expr) + expr = cmr(expr) #expr = cse_tag(expr) for m in pymbolic_expr_maps: expr = m(expr) @@ -705,7 +729,7 @@ def to_loopy_insns(assignments, vector_names=set(), pymbolic_expr_maps=[], result = [ lp.Assignment(id=None, assignee=name, expression=convert_expr(name, expr), - temp_var_type=lp.auto) + temp_var_type=lp.Optional(None)) for name, expr in assignments] logger.info("loopy instruction generation: done") diff --git a/sumpy/e2p.py b/sumpy/e2p.py index d0d05cbf8c45e8d9f1c4552f784950bdfcab4bfb..7b0072ad55708ba205ceed3448914ad0d8eda281 100644 --- a/sumpy/e2p.py +++ b/sumpy/e2p.py @@ -119,7 +119,7 @@ class E2PBase(KernelCacheWrapper): assignee="kernel_scaling", expression=sympy_conv( self.expansion.kernel.get_global_scaling_const()), - temp_var_type=lp.auto)] + temp_var_type=lp.Optional(None))] def get_cache_key(self): return (type(self).__name__, self.expansion, tuple(self.kernels)) diff --git a/sumpy/expansion/level_to_order.py b/sumpy/expansion/level_to_order.py index 8f012e55827f7676e2429f76502f5146c085387c..5e29b86138ec705ef57747929dead3403160b17a 100644 --- a/sumpy/expansion/level_to_order.py +++ b/sumpy/expansion/level_to_order.py @@ -23,73 +23,61 @@ THE SOFTWARE. """ __doc__ = """ -.. autofunction:: h2d_level_to_order_lookup -.. autofunction:: l2d_level_to_order_lookup - +.. autoclass:: FMMLibExpansionOrderFinder .. autoclass:: SimpleExpansionOrderFinder """ import numpy as np -def h2d_level_to_order_lookup(tree, helmholtz_k, epsilon): - """ - Compute a mapping from level number to expansion order, - Helmholtz 2D case. - - This wraps the function *h2dterms* from :mod:`pyfmmlib`. - - :arg tree: An instance of :class:`boxtree.Tree`. - :arg helmholtz_k: Helmholtz parameter - :arg epsilon: Precision - - :return: A :class:`numpy.array` of length `tree.nlevels` +class FMMLibExpansionOrderFinder(object): + r"""Return expansion orders that meet the tolerance for a given level + using routines wrapped from :mod:`pyfmmlib`. """ - if tree.dimensions != 2: - raise ValueError("tree must be 2d") - - orders = np.empty(tree.nlevels, dtype=int) - bbox_area = np.max( - tree.bounding_box[1] - tree.bounding_box[0]) ** 2 - - from pyfmmlib import h2dterms - for level in range(tree.nlevels): - nterms, ier = h2dterms(bbox_area / 2 ** level, helmholtz_k, epsilon) - if ier != 0: - raise RuntimeError( - "h2dterms returned error code {ier}".format(ier=ier)) - orders[level] = nterms - - return orders + def __init__(self, tol, extra_order=0): + """ + :arg tol: error tolerance + :arg extra_order: order increase to accommodate, say, the taking of + derivatives of the FMM expansions. + """ + self.tol = tol + self.extra_order = extra_order + def __call__(self, kernel, kernel_args, tree, level): + import pyfmmlib -def l2d_level_to_order_lookup(tree, epsilon): - """ - Compute a mapping from level number to expansion order, - Laplace 2D case. + from sumpy.kernel import LaplaceKernel, HelmholtzKernel - This wraps the function *l2dterms* from :mod:`pyfmmlib`. + assert isinstance(kernel, (LaplaceKernel, HelmholtzKernel)) + assert tree.dimensions in (2, 3) - :arg tree: An instance of :class:`boxtree.Tree`. - :arg epsilon: Precision + if isinstance(kernel, LaplaceKernel): + if tree.dimensions == 2: + nterms, ier = pyfmmlib.l2dterms(self.tol) + if ier: + raise RuntimeError("l2dterms returned error code '%d'" % ier) - :return: A :class:`numpy.array` of length `tree.nlevels` - """ + elif tree.dimensions == 3: + nterms, ier = pyfmmlib.l3dterms(self.tol) + if ier: + raise RuntimeError("l3dterms returned error code '%d'" % ier) - if tree.dimensions != 2: - raise ValueError("tree must be 2d") + elif isinstance(kernel, HelmholtzKernel): + helmholtz_k = dict(kernel_args)[kernel.helmholtz_k_name] + size = tree.root_extent / 2 ** level - from pyfmmlib import l2dterms - nterms, ier = l2dterms(epsilon) - if ier != 0: - raise RuntimeError( - "l2dterms returned error code {ier}".format(ier=ier)) + if tree.dimensions == 2: + nterms, ier = pyfmmlib.h2dterms(size, helmholtz_k, self.tol) + if ier: + raise RuntimeError("h2dterms returned error code '%d'" % ier) - orders = np.empty(tree.nlevels, dtype=int) - orders.fill(nterms) + elif tree.dimensions == 3: + nterms, ier = pyfmmlib.h3dterms(size, helmholtz_k, self.tol) + if ier: + raise RuntimeError("h3dterms returned error code '%d'" % ier) - return orders + return nterms + self.extra_order class SimpleExpansionOrderFinder(object): @@ -114,7 +102,7 @@ class SimpleExpansionOrderFinder(object): extra_order=1): """ :arg extra_order: order increase to accommodate, say, the taking of - oderivatives f the FMM expansions. + derivatives of the FMM expansions. """ self.tol = tol diff --git a/sumpy/kernel.py b/sumpy/kernel.py index 99222f684340fe1f36d888355a24cbffe29573c0..aebaa9fe76da1088f33dc38b3b950591732d3287 100644 --- a/sumpy/kernel.py +++ b/sumpy/kernel.py @@ -431,13 +431,14 @@ class BiharmonicKernel(ExpressionKernel): init_arg_names = ("dim",) def __init__(self, dim=None): + # See https://arxiv.org/abs/1202.1811 r = pymbolic_real_norm_2(make_sym_vector("d", dim)) if dim == 2: - expr = r**2 * var("log")(r) - scaling = 1/(8*var("pi")) + expr = r**2 * (var("log")(r) - 1) + scaling = -1/(8*var("pi")) elif dim == 3: expr = r - scaling = 1 # FIXME: Unknown + scaling = 1/(8*var("pi")) else: raise RuntimeError("unsupported dimensionality") diff --git a/sumpy/p2p.py b/sumpy/p2p.py index 4c4eb945bb90d2d71bcb5ed3327d744e4ad26391..e3b457dd52e885bf66462e475b3b58edb78bd098 100644 --- a/sumpy/p2p.py +++ b/sumpy/p2p.py @@ -125,7 +125,7 @@ class P2PBase(KernelComputation, KernelCacheWrapper): return [lp.Assignment(id=None, assignee="pair_result_%d" % i, expression=expr, - temp_var_type=lp.auto) + temp_var_type=lp.Optional(None)) for i, expr in enumerate(exprs)] def get_default_src_tgt_arguments(self): diff --git a/sumpy/qbx.py b/sumpy/qbx.py index 0d4359b6566e146d66f717d25c92569803893d0d..9708764c01d0448d7c7e8314efb461989c838acd 100644 --- a/sumpy/qbx.py +++ b/sumpy/qbx.py @@ -143,7 +143,7 @@ class LayerPotentialBase(KernelComputation, KernelCacheWrapper): return [lp.Assignment(id=None, assignee="pair_result_%d" % i, expression=expr, - temp_var_type=lp.auto) + temp_var_type=lp.Optional(None)) for i, expr in enumerate(exprs)] def get_default_src_tgt_arguments(self): diff --git a/sumpy/tools.py b/sumpy/tools.py index c826ef2fe8ea4d3f96e3b8b816dd3500f5efaae8..540208f342e429326b7571168b754f02c0041d79 100644 --- a/sumpy/tools.py +++ b/sumpy/tools.py @@ -299,7 +299,7 @@ class KernelComputation(object): lp.Assignment(id=None, assignee="knl_%d_scaling" % i, expression=sympy_conv(kernel.get_global_scaling_const()), - temp_var_type=dtype) + temp_var_type=lp.Optional(dtype)) for i, (kernel, dtype) in enumerate( zip(self.kernels, self.value_dtypes))] @@ -559,10 +559,13 @@ class MatrixBlockIndexRanges(object): # Author: Raymond Hettinger # License: MIT -import collections +try: + from collections.abc import MutableSet +except ImportError: + from collections import MutableSet -class OrderedSet(collections.MutableSet): +class OrderedSet(MutableSet): def __init__(self, iterable=None): self.end = end = [] diff --git a/test/test_fmm.py b/test/test_fmm.py index 71e3f044432b4bd612337bdd55d56e421540bcc4..2bc6d69d6bcafedf338cc78aea0591d6d5ac3845 100644 --- a/test/test_fmm.py +++ b/test/test_fmm.py @@ -42,10 +42,6 @@ from sumpy.expansion.local import ( LaplaceConformingVolumeTaylorLocalExpansion, HelmholtzConformingVolumeTaylorLocalExpansion) -from sumpy.expansion.level_to_order import ( - h2d_level_to_order_lookup, - l2d_level_to_order_lookup) - import pytest import logging @@ -60,35 +56,6 @@ else: faulthandler.enable() -@pytest.mark.parametrize("lookup_func, extra_args", [ - (h2d_level_to_order_lookup, (100,)), - (l2d_level_to_order_lookup, ()), - ]) -def test_level_to_order_lookup(ctx_getter, lookup_func, extra_args): - pytest.importorskip("pyfmmlib") - ctx = ctx_getter() - queue = cl.CommandQueue(ctx) - - nsources = 500 - dtype = np.float64 - epsilon = 1e-9 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - sources = p_normal(queue, nsources, 2, dtype, seed=15) - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, max_particles_in_box=30, debug=True) - - levels = lookup_func(tree, *(extra_args + (epsilon,))) - - assert len(levels) == tree.nlevels - # Should be monotonically nonincreasing. - assert all(levels[i] >= levels[i+1] for i in range(tree.nlevels - 1)) - - @pytest.mark.parametrize("knl, local_expn_class, mpole_expn_class", [ (LaplaceKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), (LaplaceKernel(2), LaplaceConformingVolumeTaylorLocalExpansion, diff --git a/test/test_misc.py b/test/test_misc.py index 33e8535a8cbce064263958962421d9b43e77ee6c..a25b2e800ab6f9942395821cf0cb4345e80b6ccf 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -124,7 +124,9 @@ class FakeTree: self.stick_out_factor = stick_out_factor -@pytest.mark.parametrize("knl", [LaplaceKernel(2), HelmholtzKernel(2)]) +@pytest.mark.parametrize("knl", [ + LaplaceKernel(2), HelmholtzKernel(2), + LaplaceKernel(3), HelmholtzKernel(3)]) def test_order_finder(knl): from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder @@ -136,6 +138,28 @@ def test_order_finder(knl): for level in range(30)] print(orders) + # Order should not increase with level + assert (np.diff(orders) <= 0).all() + + +@pytest.mark.parametrize("knl", [ + LaplaceKernel(2), HelmholtzKernel(2), + LaplaceKernel(3), HelmholtzKernel(3)]) +def test_fmmlib_order_finder(knl): + pytest.importorskip("pyfmmlib") + from sumpy.expansion.level_to_order import FMMLibExpansionOrderFinder + + ofind = FMMLibExpansionOrderFinder(1e-5) + + tree = FakeTree(knl.dim, 200, 0.5) + orders = [ + ofind(knl, frozenset([("k", 5)]), tree, level) + for level in range(30)] + print(orders) + + # Order should not increase with level + assert (np.diff(orders) <= 0).all() + # {{{ expansion toys p2e2e2p test cases