Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/typos@master
- uses: crate-ci/typos@main

docs:
name: Documentation
Expand Down
46 changes: 2 additions & 44 deletions boxtree/pyfmmlib_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,10 +777,8 @@ def form_multipoles(
formmp = self.tree_indep.get_routine(
"%ddformmp" + ("_dp" if self.use_dipoles else ""),
suffix="_imany")
except AttributeError:
# pyfmmlib predates the batched (indirect-many) P2M wrappers
return self._form_multipoles_one_box_at_a_time(
level_start_source_box_nrs, source_boxes, src_weights)
except AttributeError as err:
raise RuntimeError("pyfmmlib >= 2026.1 is required") from err

mpoles = self.multipole_expansion_zeros()

Expand Down Expand Up @@ -843,46 +841,6 @@ def form_multipoles(

return mpoles

def _form_multipoles_one_box_at_a_time(
self,
level_start_source_box_nrs: Array,
source_boxes: Array,
src_weights: Array) -> Array:
formmp = self.tree_indep.get_routine(
"%ddformmp" + ("_dp" if self.use_dipoles else ""))

mpoles = self.multipole_expansion_zeros()
for lev in range(self.tree.nlevels):
start, stop = level_start_source_box_nrs[lev:lev+2]
if start == stop:
continue

level_start_ibox, mpoles_view = self.multipole_expansions_view(mpoles, lev)
rscale = self.level_to_rscale(lev)

for src_ibox in source_boxes[start:stop]:
pslice = self._get_source_slice(src_ibox)
if pslice.stop - pslice.start == 0:
continue

kwargs = {}
kwargs.update(self.kernel_kwargs)
kwargs.update(self.get_source_kwargs(src_weights, pslice))

ier, mpole = formmp(
rscale=rscale,
source=self._get_sources(pslice),
center=self.tree.box_centers[:, src_ibox],
nterms=self.level_orders[lev],
**kwargs)

if ier:
raise RuntimeError("formmp failed")

mpoles_view[src_ibox-level_start_ibox] = mpole.T

return mpoles

@log_process(logger)
@override
def coarsen_multipoles(
Expand Down
68 changes: 0 additions & 68 deletions test/test_fmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,74 +527,6 @@ def fmm_level_to_order(tree, lev):

# }}}


@pytest.mark.parametrize("dims", [2, 3])
@pytest.mark.parametrize("use_dipoles", [True, False])
@pytest.mark.parametrize("helmholtz_k", [0, 2])
def test_pyfmmlib_batched_form_multipoles(
actx_factory, dims, use_dipoles, helmholtz_k):
"""Check that the batched (indirect-many) P2M path reproduces the
one-box-at-a-time path exactly."""
pyfmmlib = pytest.importorskip("pyfmmlib")
if not hasattr(pyfmmlib, "l2dformmp_imany"):
pytest.skip("pyfmmlib lacks the batched formmp wrappers")

actx = actx_factory()

nsources = 800
ntargets = 400
dtype = np.float64

sources = p_normal(actx, nsources, dims, dtype, seed=15)
targets = (
p_normal(actx, ntargets, dims, dtype, seed=18)
+ np.array([2, 0, 0])[:dims])

from boxtree import TreeBuilder
tb = TreeBuilder(actx)

tree, _ = tb(actx, sources, targets=targets,
max_particles_in_box=30, debug=True)

from boxtree.traversal import FMMTraversalBuilder
tbuild = FMMTraversalBuilder(actx)
trav, _ = tbuild(actx, tree, debug=True)

trav = actx.to_numpy(trav)

rng = np.random.default_rng(20)
weights = rng.uniform(0.0, 1.0, (nsources,))
dipole_vec = rng.normal(size=(dims, nsources)) if use_dipoles else None

from boxtree.pyfmmlib_integration import (
FMMLibExpansionWrangler,
FMMLibTreeIndependentDataForWrangler,
Kernel,
)
tree_indep = FMMLibTreeIndependentDataForWrangler(
trav.tree.dimensions,
Kernel.HELMHOLTZ if helmholtz_k else Kernel.LAPLACE)
wrangler = FMMLibExpansionWrangler(
tree_indep, trav,
helmholtz_k=helmholtz_k,
fmm_level_to_order=lambda tree, lev: 10,
dipole_vec=dipole_vec)

src_weights = wrangler.reorder_sources(weights)

mpoles_batched = wrangler.form_multipoles(
actx,
trav.level_start_source_box_nrs,
trav.source_boxes,
(src_weights,))

mpoles_one_at_a_time = wrangler._form_multipoles_one_box_at_a_time(
trav.level_start_source_box_nrs,
trav.source_boxes,
src_weights)

assert np.array_equal(mpoles_batched, mpoles_one_at_a_time)

# }}}


Expand Down
Loading