Skip to content

Cache last occluding atom in Vacc_atomSurf - #332

Merged
sobolevnrm merged 3 commits into
Electrostatics:mainfrom
g8j39:vacc-occluder-cache
Nov 3, 2026
Merged

sobolevnrm merged 3 commits into
Electrostatics:mainfrom
g8j39:vacc-occluder-cache

Conversation

@g8j39

@g8j39 g8j39 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When building an atom's solvent-accessibility surface, Vacc_atomSurf (src/generic/vacc.c#L866-L902) tests every point on the atom's reference sphere with ivdwAccExclus (src/generic/vacc.c#L78-L128), which looks up the probe point's Vclist hash cell and scans the cell's atom list from index 0 until an overlapping neighbor is found. Because consecutive points on the reference sphere are spatially adjacent, neighboring buried points are usually occluded by the same atom, repeating the cell lookup and list scan many times per atom.

This change caches a stack-local pointer (Vatom *last_hit) in Vacc_atomSurf to the last atom that buried a probe point and tests that atom first before querying Vclist when thee->clist uses an automatic domain (CLIST_AUTO_DOMAIN).

Behavior and outputs are unchanged:

  • An atom is cached only after ivdwAccExclusCache has already verified atom->id != atomID and overlap (dist2 < (atom->radius + radius)^2), and the fast path applies the identical distance check.
  • Caching is enabled only for automatic Vclist domains (CLIST_AUTO_DOMAIN), where any point overlapping a cached neighbor in thee->alist with radius <= Vclist_maxRadius(thee->clist) lies inside the inflated Vclist lookup grid. Manual domains (CLIST_MANUAL_DOMAIN) leave last_hit disabled so out-of-domain sample points continue to hit Vclist_getCell(...) == VNULL and return accessible (1).
  • last_hit lives on the stack of Vacc_atomSurf, adding no shared mutable state.

Changes

  • Add static helper ivdwAccExclusCache(..., Vatom **last_hit) in src/generic/vacc.c that gates last_hit on thee->clist->mode == CLIST_AUTO_DOMAIN, checks *last_hit before Vclist_getCell, and updates *last_hit when a cell atom buries the probe point.
  • Keep ivdwAccExclus as a thin wrapper passing VNULL for its other callers (ivdwAcc and Vacc_ivdwAcc), and call ivdwAccExclusCache from Vacc_atomSurf with stack-local &last_hit.
  • Add vacc_surf_test (tests/vacc_surf_test.c, registered in tests/CMakeLists.txt) covering consecutive cached hits, fallback after a cache miss, changes in the occluding neighbor, and out-of-domain CLIST_MANUAL_DOMAIN surface samples.

Validation

  • Correctness: Built cleanly in serial, -DENABLE_iAPBS=ON -DBUILD_TOOLS=ON, and -DENABLE_OPENMP=ON configurations with zero new compiler warnings (-Wall -Wextra -Wunused -Wdeclaration-after-statement, and zero total warnings on tests/vacc_surf_test.c). Verified that vacc_surf_test reproduces the CLIST_MANUAL_DOMAIN mismatch on 34244af and passes after the fix. Passed ctest (14/14 targets) and apbs_tester.py --ocd (1e-12 relative tolerance on born, FKBP, hca-bind, ion-protein, pka-lig, and solv), with identical printed energies and apolar SASA/SAV outputs across all benchmarked runs (0.00e+00 diff) and identical 4-thread vs 1-thread OpenMP energies on examples/actin-dimer/apbs-smol-auto.in.
  • Performance (gcc -O2, 1 core on AMD EPYC 7B13, median of 5 interleaved runs against main 327ddc6):
    cd examples/hca-bind && apbs apbs-smol.in
Workload (srfm smol, sdens 10.0) Setup (main -> PR) Wall (main) Wall (this PR) Wall change
examples/pka-lig/apbs-smol-surf.in 4.08 -> 2.33 s (43.1% less) 6.24 s 4.49 s 28.1% less time
examples/hca-bind/apbs-smol.in 4.30 -> 2.23 s (48.2% less) 5.25 s 3.19 s 39.2% less time
examples/actin-dimer/apbs-smol-auto.in 14.60 -> 6.43 s (55.9% less) 40.97 s 32.78 s 20.0% less time
1EBZ MM/PBSA (3 frames, 129x97x97 + apolar*) 15.17 -> 10.21 s (32.7% less) 27.91 s 22.34 s 20.0% less time

*Apolar SASA/SAV time on 1EBZ MM/PBSA also drops by 51.3% (1.29 -> 0.63 s).

Prepared with AI assistance.

Consecutive points on an atom's reference sphere are spatially adjacent
and are usually buried by the same neighboring atom. Cache a stack-local
pointer to the previous occluding Vatom in Vacc_atomSurf and test it
before looking up the Vclist cell, preserving accessibility results and
OpenMP thread safety.

@sobolevnrm sobolevnrm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a well-scoped optimization, and the reported performance improvements are encouraging.
The stack-local cache avoids introducing shared mutable state, and the cached overlap test preserves the existing distance comparison and self-exclusion for the current caller.

I’d request one compatibility fix before merging.
In src/generic/vacc.c:102–111, the cached-neighbor check returns before Vclist_getCell.
For a manually bounded grid (CLIST_MANUAL_DOMAIN), a surface point can lie outside the lookup domain while still overlapping a previously cached neighbor.
The original implementation classifies that point as accessible because the cell lookup returns NULL; the new implementation can classify it as buried.
I reproduced this difference using consecutive surface samples.

Both current production construction sites use CLIST_AUTO_DOMAIN, so this is a library-API compatibility issue rather than a demonstrated regression in ordinary APBS calculations.
Please restrict caching to automatic domains, or otherwise preserve the existing out-of-domain behavior without losing the optimization.

Please also add an automated regression test covering cached hits, fallback after a miss, changes in the occluding neighbor, and the manual-domain boundary case.
With that compatibility issue addressed and the remaining CI checks passing, I would be comfortable merging this.

Under CLIST_MANUAL_DOMAIN, a surface point can fall outside the manual
lookup bounds (where Vclist_getCell returns VNULL and classifies the
point as accessible) while still overlapping the previously cached
neighbor. Disable last_hit caching unless thee->clist->mode is
CLIST_AUTO_DOMAIN, and add a CTest unit regression test covering
consecutive cached hits, fallback after a cache miss, changes in the
occluding neighbor, and out-of-domain manual-grid samples.
@g8j39

g8j39 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! I updated ivdwAccExclusCache so last_hit caching is enabled only when thee->clist->mode == CLIST_AUTO_DOMAIN, and added a vacc_surf_test CTest unit test covering cached hits, miss fallbacks, neighbor changes, and the CLIST_MANUAL_DOMAIN out-of-domain case.

@sobolevnrm

Copy link
Copy Markdown
Member

Wonderful; I will merge. Thanks!

@sobolevnrm
sobolevnrm merged commit 057751a into Electrostatics:main Nov 3, 2026
14 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