From ea6c161de5caa917c77cf73ae28dffceff0d064b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner <inform@tiker.net> Date: Tue, 5 Jul 2011 21:11:23 -0400 Subject: [PATCH] Add M2M and M2L computer algebra (untested), plus rudiments of a test. --- sumpy/expansion.py | 93 +++++++++++++++++++++++++++++------- test/test_kernels.py | 109 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 184 insertions(+), 18 deletions(-) diff --git a/sumpy/expansion.py b/sumpy/expansion.py index 5528c2f0..97de6c22 100644 --- a/sumpy/expansion.py +++ b/sumpy/expansion.py @@ -26,7 +26,7 @@ class Expansion(object): -class TaylorExpansion(Expansion): +class TaylorMultipoleExpansion(Expansion): def __init__(self, kernel, order, dimensions): """ :arg: kernel in terms of 'b' variable @@ -41,36 +41,97 @@ class TaylorExpansion(Expansion): as gnitstam) self.multi_indices = sorted(gnitstam(self.order, self.dimensions), key=sum) + self.mi_to_index = dict( + (mi, i) for i, mi in enumerate(self.multi_indices)) # given in terms of b variable + from sumpy.symbolic import diff_multi_index self.basis = [ - self.diff_kernel(mi) + diff_multi_index(kernel, mi, "b") for mi in self.multi_indices] # given in terms of a variable from sumpy.symbolic import make_sym_vector - a = make_sym_vector("a", 3) + a = make_sym_vector("a", dimensions) from sumpy.tools import mi_power, mi_factorial self.coefficients = [ mi_power(a, mi)/mi_factorial(mi) for mi in self.multi_indices] - @memoize_method - def diff_kernel(self, multi_index): - if sum(multi_index) == 0: - return self.kernel + def m2m_exprs(self, get_coefficient_expr): + """Expressions for coefficients of shifted expansion, in terms of s + (the shift from the old center to the new center), as well as the + coefficients returned by *get_coefficient_expr* for each multi-index. + """ + + from sumpy.symbolic import make_sym_vector + a = make_sym_vector("a", self.dimensions) + s = make_sym_vector("s", self.dimensions) + # new center = c+s + + from sumpy.symbolic import IdentityMapper, find_power_of + class ToCoefficientMapper(IdentityMapper): + def map_Mul(subself, expr): + a_powers = tuple(int(find_power_of(ai, expr)) for ai in a) + + return ( + expr/mi_power(a, a_powers) + * get_coefficient_expr(self.mi_to_index[a_powers])) + + map_Symbol = map_Mul + + tcm = ToCoefficientMapper() + + from sumpy.tools import mi_power + return [tcm(mi_power(a+s, mi).expand()) for mi in self.multi_indices] + + def m2l_exprs(self, loc_exp, get_coefficient_expr): + """Expressions for coefficients of the local expansion *loc_exp* of the + multipole expansion *self*, whose coefficients are obtained by + *get_coefficient_expr* for each coefficient index. The expressions are + given in terms of *s* (the shift from the multipole center to the local + center). + """ + + b2s_map = dict( + (sp.Symbol("b%d" % i), sp.Symbol("s%d" % i)) + for i in range(self.dimensions)) + + expansion = sum( + get_coefficient_expr(i) + * basis_func.subs(b2s_map) + for i, basis_func in enumerate(self.basis)) + + from sumpy.symbolic import diff_multi_index + from sumpy.tools import mi_factorial + return [diff_multi_index(expansion, loc_mi, "s") / mi_factorial(loc_mi) + for loc_mi in loc_exp.multi_indices] + + - first_nonzero_axis = min( - i for i in range(self.dimensions) - if multi_index[i] > 0) - lowered_mi = list(multi_index) - lowered_mi[first_nonzero_axis] -= 1 - lowered_mi = tuple(lowered_mi) - lower_diff_kernel = self.diff_kernel(lowered_mi) - return sp.diff(lower_diff_kernel, - sp.Symbol("b%d" % first_nonzero_axis)) +class TaylorLocalExpansion(Expansion): + def __init__(self, order, dimensions): + self.order = order + self.dimensions = dimensions + + from pytools import ( + generate_nonnegative_integer_tuples_summing_to_at_most + as gnitstam) + + self.multi_indices = sorted(gnitstam(self.order, self.dimensions), key=sum) + self.mi_to_index = dict( + (mi, i) for i, mi in enumerate(self.multi_indices)) + from sumpy.symbolic import make_sym_vector + + from sumpy.tools import mi_power, mi_factorial + + # given in terms of b variable + b = make_sym_vector("b", dimensions) + self.basis = [ + mi_power(b, mi)/mi_factorial(mi) + for mi in self.multi_indices] diff --git a/test/test_kernels.py b/test/test_kernels.py index 755b1968..eb466aea 100644 --- a/test/test_kernels.py +++ b/test/test_kernels.py @@ -99,8 +99,8 @@ def test_p2m2p(ctx_getter): cell_idx_to_particle_cnt_tgt_dev = to_device(queue, cell_idx_to_particle_cnt_tgt) from sumpy.symbolic import make_coulomb_kernel_in - from sumpy.expansion import TaylorExpansion - texp = TaylorExpansion( + from sumpy.expansion import TaylorMultipoleExpansion + texp = TaylorMultipoleExpansion( make_coulomb_kernel_in("b", dimensions), order=3, dimensions=dimensions) @@ -161,6 +161,111 @@ def test_p2m2p(ctx_getter): +@pytools.test.mark_test.opencl +def test_p2m2m2p(ctx_getter): + ctx = ctx_getter() + queue = cl.CommandQueue(ctx) + + res = 100 + + dimensions = 3 + sources = np.random.rand(dimensions, 5).astype(np.float32) + #targets = np.random.rand(dimensions, 10).astype(np.float32) + targets = np.mgrid[-2:2:res*1j, -2:2:res*1j, 2:2:1j].reshape(3, -1).astype(np.float32) + centers = np.array([[0.5]]*dimensions).astype(np.float32) + + from sumpy.tools import vector_to_device + targets_dev = vector_to_device(queue, targets) + sources_dev = vector_to_device(queue, sources) + centers_dev = vector_to_device(queue, centers) + strengths_dev = cl_array.empty(queue, sources.shape[1], dtype=np.float32) + strengths_dev.fill(1) + + cell_idx_to_particle_offset = np.array([0], dtype=np.uint32) + cell_idx_to_particle_cnt_src = np.array([sources.shape[1]], dtype=np.uint32) + cell_idx_to_particle_cnt_tgt = np.array([targets.shape[1]], dtype=np.uint32) + + from pyopencl.array import to_device + cell_idx_to_particle_offset_dev = to_device(queue, cell_idx_to_particle_offset) + cell_idx_to_particle_cnt_src_dev = to_device(queue, cell_idx_to_particle_cnt_src) + cell_idx_to_particle_cnt_tgt_dev = to_device(queue, cell_idx_to_particle_cnt_tgt) + + from sumpy.symbolic import make_coulomb_kernel_in + from sumpy.expansion import TaylorMultipoleExpansion + texp = TaylorMultipoleExpansion( + make_coulomb_kernel_in("b", dimensions), + order=3, dimensions=dimensions) + + def get_coefficient_expr(idx): + return sp.Symbol("coeff%d" % idx) + + for i in texp.m2m_exprs(get_coefficient_expr): + print i + + print "-------------------------------------" + from sumpy.expansion import TaylorLocalExpansion + locexp = TaylorLocalExpansion(order=2, dimensions=3) + + for i in texp.m2l_exprs(locexp, get_coefficient_expr): + print i + + 1/0 + + coeff_dtype = np.float32 + + # {{{ apply P2M + + from sumpy.p2m import P2MKernel + p2m = P2MKernel(ctx, texp) + mpole_coeff = p2m(cell_idx_to_particle_offset_dev, + cell_idx_to_particle_cnt_src_dev, + sources_dev, strengths_dev, centers_dev, coeff_dtype) + + # }}} + + # {{{ apply M2P + + output_maps = [ + lambda expr: expr, + lambda expr: sp.diff(expr, sp.Symbol("t0")) + ] + + m2p_ilist_starts = np.array([0, 1], dtype=np.uint32) + m2p_ilist_mpole_offsets = np.array([0], dtype=np.uint32) + + m2p_ilist_starts_dev = to_device(queue, m2p_ilist_starts) + m2p_ilist_mpole_offsets_dev = to_device(queue, m2p_ilist_mpole_offsets) + + from sumpy.m2p import M2PKernel + m2p = M2PKernel(ctx, texp, output_maps=output_maps) + potential_dev, x_derivative = m2p(targets_dev, m2p_ilist_starts_dev, m2p_ilist_mpole_offsets_dev, mpole_coeff, + cell_idx_to_particle_offset_dev, + cell_idx_to_particle_cnt_tgt_dev) + + # }}} + + # {{{ compute (direct) reference solution + + from sumpy.p2p import P2PKernel + from sumpy.symbolic import make_coulomb_kernel_ts + coulomb_knl = make_coulomb_kernel_ts(dimensions) + + knl = P2PKernel(ctx, dimensions, + exprs=[f(coulomb_knl) for f in output_maps], exclude_self=False) + + potential_dev_direct, x_derivative_dir = knl(targets_dev, sources_dev, strengths_dev) + + if 0: + import matplotlib.pyplot as pt + pt.imshow((potential_dev-potential_dev_direct).get().reshape(res, res)) + pt.colorbar() + pt.show() + + assert la.norm((potential_dev-potential_dev_direct).get())/res**2 < 1e-3 + + + + # You can test individual routines by typing # $ python test_kernels.py 'test_p2p(cl.create_some_context)' -- GitLab