Skip to content

Use pyfmmlib's batched formmp wrappers in form_multipoles - #124

Merged
inducer merged 3 commits into
inducer:mainfrom
xywei:formmp-imany
Aug 5, 2026
Merged

Use pyfmmlib's batched formmp wrappers in form_multipoles#124
inducer merged 3 commits into
inducer:mainfrom
xywei:formmp-imany

Conversation

@xywei

@xywei xywei commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Follow-up to inducer/pyfmmlib#94, as discussed there.

What

form_multipoles currently forms one multipole per scalar formmp call in a Python loop over source boxes. With pyfmmlib now generating {l,h}{2,3}dformmp[_dp]_imany, this PR switches it to one indirect-many call per level — the same addressing pattern form_locals already uses with formta_imany, with each expansion center drawing on the single source segment of its own box. Boxes without sources are excluded from the batched call so their expansion entries stay zero, exactly as the loop behaved.

If pyfmmlib predates the wrappers (AttributeError from the routine lookup), form_multipoles transparently falls back to the previous loop, kept verbatim as _form_multipoles_one_box_at_a_time — so nothing changes for released pyfmmlib until its next release, and no requirement bump is needed.

Verification

  • New test test_pyfmmlib_batched_form_multipoles pins bit-exact equality between the batched path and the one-box-at-a-time path across 2D/3D × charges/dipoles × Laplace/Helmholtz (8 parametrizations, all passing). It skips when pyfmmlib lacks the wrappers.
  • Existing test_pyfmmlib_fmm and test_pyfmmlib_numerical_stability pass through the new path (12 tests).
  • The fallback path verified to engage and produce identical results when the _imany lookup raises.

Measured

100k-source 3D Laplace tree, 7216 source boxes, order 15, 4C/8T laptop (pyfmmlib built with OpenMP per inducer/pyfmmlib#93): form_multipoles 516 ms → 236 ms. Single-threaded (OMP_NUM_THREADS=1): 623 ms → 561 ms, the call-overhead saving alone.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YYp74e5ZTE6ZHDNR3ETgM6

xywei and others added 3 commits August 5, 2026 15:18
pyfmmlib now generates {l,h}{2,3}dformmp[_dp]_imany (inducer/pyfmmlib#94),
the P2M twins of the formta wrappers that form_locals already uses. Switch
form_multipoles to one indirect-many call per level, with each expansion
center drawing on the single source segment of its own box; boxes without
sources are excluded so their expansion entries stay zero, as before.

When pyfmmlib predates these wrappers, form_multipoles transparently falls
back to the previous one-box-at-a-time loop, which is kept verbatim as
_form_multipoles_one_box_at_a_time.

The batched path reproduces the loop bit for bit (tested across 2D/3D,
charges/dipoles, Laplace/Helmholtz). On a 100k-source 3D Laplace tree with
7216 source boxes at order 15, form_multipoles drops from 516 ms to 236 ms
on a 4C/8T laptop with OpenMP, and from 623 ms to 561 ms single-threaded
(the call-overhead saving alone).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYp74e5ZTE6ZHDNR3ETgM6
FMMLibExpansionWrangler now declares tree_indep as its concrete
FMMLibTreeIndependentDataForWrangler, which is what every get_routine
call site already assumes; without the narrowing, basedpyright flags the
batched-P2M probe (the guarded call inside try/except AttributeError) as
accessing an attribute the base class lacks. The runtime AttributeError
that the guard catches comes from pyfmmlib lacking the generated wrapper,
not from the method lookup itself. Also materialize
box_source_counts_nonchild through np.asarray before the emptiness
comparison so the operator is typed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYp74e5ZTE6ZHDNR3ETgM6
@inducer
inducer enabled auto-merge (rebase) August 5, 2026 13:25
@inducer

inducer commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Thanks!

@inducer
inducer merged commit 3e49bd4 into inducer:main Aug 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants