Skip to content

Add Verlet-style neighbor list cache to calculators#8

Merged
sirmarcel merged 2 commits intomainfrom
verlet-neighborlist-cache
Mar 20, 2026
Merged

Add Verlet-style neighbor list cache to calculators#8
sirmarcel merged 2 commits intomainfrom
verlet-neighborlist-cache

Conversation

@sirmarcel
Copy link
Copy Markdown
Contributor

@sirmarcel sirmarcel commented Mar 16, 2026

Summary

  • Add NeighborListCache (src/lorem/neighborlist.py) implementing the Verlet displacement criterion: rebuild only when the combined position + cell deformation exceeds the skin budget
  • Calculator builds neighbor lists with cutoff + skin, caches the batch, and on small geometry changes updates only sr.positions and sr.cell — the model recomputes R_ij from current positions/cell so forces/energy/stress stay correct
  • Ewald k-vectors are recomputed automatically from sr.cell (pbc.k_grid stores integer frequency indices only), and Ewald parameters (lr_wavelength, smearing) are derived from the physical cutoff to keep the long-range decomposition unchanged
  • skin kwarg (default 0.25 Å) added to Calculator, from_model, from_checkpoint, and LOREM_driver

Combined Verlet criterion (NPT-aware)

    Pair (i, j) with periodic image shift S:

    R_ij(t) = R_j(t) - R_i(t) + S · cell(t)

    ΔR_ij = (ΔR_j - ΔR_i) + S · Δcell
            ╰────────────╯   ╰────────╯
            ≤ 2 · d_max      ≤ max_shift · Σ|Δcell_A|

    Rebuild when:  2·d_max + max_shift · Σ|Δcell_A| > skin

max_cell_shift (max |S| in the neighbor list) is extracted from the batch at build time. Falls back to exact cell comparison when shift info is unavailable (backward compatible).

Test plan

Unit tests (test_neighborlist.py — 19 tests)

  • needs_update returns True on first call (no reference)
  • Returns False when atoms unchanged
  • Returns False for displacement < 0.5*skin
  • Returns True for displacement > 0.5*skin
  • Boundary: displacement exactly at 0.5*skin (strict >)
  • Cell change without shift info triggers rebuild (backward compatible)
  • PBC change triggers rebuild
  • Natoms change triggers rebuild
  • Atomic numbers change triggers rebuild
  • save_reference + reset lifecycle
  • Cumulative displacement from reference (not per-step)
  • Default skin is 0.25 Å
  • Small cell change with max_cell_shift=1 stays within skin
  • Large cell change with max_cell_shift=1 exceeds skin
  • Cell change with max_cell_shift=0 has no effect (non-periodic)
  • Combined position + cell change within skin
  • Combined position + cell change exceeding skin
  • Higher max_cell_shift amplifies cell contribution
  • Reset clears max_cell_shift (reverts to exact comparison)

Integration tests (test_calculator.py — 10 tests)

  • Default skin value
  • Skin configurable via from_model
  • Skin vs no-skin gives matching energy/forces on static structure
  • Small displacement reuses neighbor list, results match fresh calculator
  • Forces correct after position-only update (energy changes, shape correct)
  • Large displacement triggers full rebuild
  • NPT: small cell scaling reuses cache, energy/forces match fresh calc
  • NPT: stress correct after cell-only update
  • Large cell change triggers rebuild
  • Combined position + cell change within skin, correct results

All 47 tests pass, lint clean.

🤖 Generated with Claude Code

sirmarcel and others added 2 commits March 16, 2026 17:26
Build neighbor lists with cutoff + skin and skip expensive rebuilds when
atoms have moved less than 0.5 * skin from the reference positions. When
within skin, only sr.positions is updated in the cached batch — the model
recomputes R_ij from current positions, so forces remain correct. Ewald
parameters are derived from the physical cutoff to keep the long-range
decomposition unchanged.

Skin defaults to 0.25 Å and is configurable via the `skin` kwarg on
Calculator, from_model, from_checkpoint, and LOREM_driver.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the exact cell comparison with a combined Verlet criterion that
accounts for both position displacement and cell deformation:

    |ΔR_ij| ≤ 2·d_max + max_shift · Σ|Δcell_A| < skin

max_cell_shift (max |S| in the neighbor list) is extracted from the batch
at build time. When cell changes are within the skin budget, only
sr.positions and sr.cell are updated — the Ewald calculator recomputes
k-vectors from sr.cell on-the-fly (pbc.k_grid stores integer frequency
indices only), so forces, energy, and stress remain correct.

Falls back to exact cell comparison when max_cell_shift is not available
(direct NeighborListCache use without calculator), preserving backward
compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@PicoCentauri PicoCentauri left a comment

Choose a reason for hiding this comment

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

Isn't this something the engine should do?

@sirmarcel sirmarcel merged commit efc010c into main Mar 20, 2026
5 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