Skip to content

[WIP] ABLASTR: reuse the IGF Green's function - #7178

Open
ax3l wants to merge 1 commit into
BLAST-WarpX:developmentfrom
ax3l:topic-igf-greens-cache
Open

[WIP] ABLASTR: reuse the IGF Green's function#7178
ax3l wants to merge 1 commit into
BLAST-WarpX:developmentfrom
ax3l:topic-igf-greens-cache

Conversation

@ax3l

@ax3l ax3l commented Aug 19, 2026

Copy link
Copy Markdown
Member

computePhiIGF rebuilt and re-transformed its Green's function on every call. That dominates the solver: measured per call on a 128^3 grid, building costs 390 ms against an 84 ms solve on CPU, roughly three quarters of the runtime, and it does not shrink with particle count. The Green's function depends on the grid only through the cell size, so this remembers which grid the one in the solver was built for and skips the rebuild while that has not changed, keeping further ones in a small least-recently-used store for callers whose grid comes back to a size it has had before.

Green's functions move in and out of the store with std::swap, so nothing is copied. The integrated Green's function is homogeneous of degree two, verified against SumOfIntegratedPotential3D to 2.8e-13, so an entry also serves any scale reached by an exact power of two, applied to phi afterwards. Two-d mode copies rather than swaps, since there m_G_fft aliases the Green's function R2C's spectral data. Controls are ablastr.igf_cache_max_entries, igf_cache_max_bytes, igf_cache_tolerance and igf_cache_verbose, with igf_rebuild_always restoring the old behavior, all documented in the parameter list.

Who benefits depends on how steady the cell size is, and the relativistic electrostatic solver is not steady by default. It derives the longitudinal stretch from a velocity, and reconstructing it costs about $\epsilon\gamma^2$, which is a few ulp at $\gamma$ of a thousand but 1e-5 at 125 GeV; the velocity is also re-measured from the particles every step and carries sampling noise of its own. igf_cache_tolerance exists for exactly that case and defaults to $64\epsilon$, so nothing changes silently. Depends on AMReX-Codes/amrex#5627 for the accessor, and is exercised from ImpactX in BLAST-ImpactX/impactx#1621.

Measurements

beam_beam_collision on the IGF solver, which needs warpx.poisson_solver=fft and open field boundaries, 5 steps, 4 threads. At the default tolerance it reuses nothing, because of the two effects above. Raising it trades a bounded error for reuse:

igf_cache_tolerance reused built Green's function share total max rel diff vs igf_rebuild_always
default, $64\epsilon$ 0 14 30.9% 1.68 s identical
1e-6 1 11 24.6% 1.50 s
1e-5 3 9 22.2% 1.42 s
1e-4 8 4 6.9% 1.16 s 5.3e-6
1e-3 10 2 3.6% 1.09 s 2.3e-3

The comparison is on ColliderRelevant_beam1_beam2, single threaded so it means something; ParticleNumber is identical at both settings. Build counts vary by about two between runs, since QED secondary production is thread-order dependent.

Tested through ImpactX, where the grid is chosen to repeat and the default tolerance suffices. Runs are compared against igf_rebuild_always=1 and required to agree exactly on all beam moments: with the store disabled and enabled, with a mesh fitted to the beam and with a quantized one, and on an accelerating lattice where the reference energy triples. On a constant-focusing channel with a static grid that took Green's function builds from 50 to 1 and runtime from 1.53 s to 0.37 s, and on an accelerating beam from 100 to 10 and 0.95 s to 0.41 s, byte-identically.

Two things for review. The key must never miss a change in the Green's function, since a stale one is a wrong answer rather than a slow one, so it is derived inside computePhiIGF from the cell size and the solver identity rather than accepted from callers. And twod_mode and the FFT process count are now part of the identity that decides whether the solver is rebuilt, which they were not before.

🤖 Generated with Claude Code

computePhiIGF rebuilt and re-transformed its Green's function on every
call, which dominates the solver: on CPU it costs 3 to 5 times the solve
itself and about three quarters of the runtime.

The Green's function depends on the grid only through the cell size, so
remember which grid the one in the solver was built for and skip the
rebuild while that has not changed. Keep further ones in a small
least-recently-used store, moved in and out with std::swap so nothing is
copied, for callers whose grid returns to a size it has had before. The
integrated Green's function is homogeneous of degree two, so an entry
also serves any scale reached by an exact power of two, applied to phi
afterwards.

Controlled by ablastr.igf_cache_max_entries, igf_cache_max_bytes and
igf_cache_verbose, with igf_rebuild_always to restore the old behavior.
This also puts the two-d mode and the FFT process count into the key
identifying the solver, which were missing before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ax3l
ax3l force-pushed the topic-igf-greens-cache branch from cd27215 to f9d6ffe Compare August 27, 2026 22:55
ax3l added a commit to AMReX-Codes/amrex that referenced this pull request Aug 28, 2026
`OpenBCSolver` rebuilds its spectral Green's function whenever
`setGreensFunction()` is called, and there is no way to hand it one that
was built earlier. Add `greensFunctionFFT()` accessors so a caller can
keep its own set and move them into and out of the solver with
`std::swap` rather than recomputing. `solve()` only reads the spectral
Green's function, and `prepare_openbc()` concerns the R2C plans rather
than the Green's function values, so one moved in this way needs no
further preparation.

The one caveat, documented at the accessor: in 2D, `m_G_fft` is an alias
of the Green's function R2C's spectral data, which a later
`setGreensFunction()` writes into. Callers may write through the
reference there, but must not swap or otherwise re-seat it.

Additive only, so nothing changes for existing users. Exercised
downstream in BLAST-WarpX/warpx#7178 &
BLAST-ImpactX/impactx#1621, where reusing
Green's functions this way reproduces rebuilding them bit for bit, and
where it takes an accelerating beam simulation from 100 Green's function
builds to 10.

- [x] 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- [x] tested with ABLASTR/WarpX and ImpactX
- [x] manually self-reviewed

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant