Skip to content

Commit 71ee034

Browse files
committed
Cache last forces/stress ensemble to avoid redundant recomputation
1 parent 1197bbe commit 71ee034

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/upet/calculator.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ def __init__(
203203
self._direct_uncertainty_key_BASE = (
204204
"mtt::aux::non_conservative_forces_uncertainty"
205205
)
206+
# cache of the last conservative forces/stress ensemble computation, as
207+
# (atoms, forces_ensemble, stress_ensemble); avoids recomputing the
208+
# expensive Jacobian pass when called again for the same atoms.
209+
self._uq_cache: Optional[
210+
Tuple[Atoms, Optional[np.ndarray], Optional[np.ndarray]]
211+
] = None
206212

207213
self.calculator = MetatomicCalculator(
208214
loaded_model,
@@ -340,6 +346,17 @@ def _run_forces_stress_uq(
340346
else:
341347
atoms = self.atoms
342348

349+
cached = self._uq_cache
350+
if cached is not None and cached[0] == atoms:
351+
cached_forces, cached_stress = cached[1], cached[2]
352+
if (not compute_forces or cached_forces is not None) and (
353+
not compute_stress or cached_stress is not None
354+
):
355+
return (
356+
cached_forces if compute_forces else None,
357+
cached_stress if compute_stress else None,
358+
)
359+
343360
calc = self.calculator
344361
# Unwrap SymmetrizedCalculator if present
345362
if isinstance(calc, SymmetrizedCalculator):
@@ -445,7 +462,16 @@ def _compute_ensemble(*positions_and_strain):
445462
# [3, 3, n_ensemble]
446463
stress_ensemble = stress_jac.transpose(1, 2, 0) / volume
447464

448-
return forces_ensemble, stress_ensemble
465+
# keep whichever of forces/stress was already cached for these atoms but
466+
# not recomputed here, instead of discarding it
467+
if cached is not None and cached[0] == atoms:
468+
forces_ensemble = forces_ensemble if compute_forces else cached[1]
469+
stress_ensemble = stress_ensemble if compute_stress else cached[2]
470+
self._uq_cache = (atoms.copy(), forces_ensemble, stress_ensemble)
471+
return (
472+
forces_ensemble if compute_forces else None,
473+
stress_ensemble if compute_stress else None,
474+
)
449475

450476
@property
451477
def _direct_ensemble_key(self) -> str:

tests/upet/test_uncertainty_quantification.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,26 @@ def test_forces_and_stress_ensemble():
168168
nc_calc.get_forces_and_stress_ensemble(atoms)
169169

170170

171+
def test_forces_ensemble_cache():
172+
"""Test that repeated calls with unchanged atoms reuse the cached ensemble."""
173+
atoms = bulk("Si", cubic=True, a=5.43, crystalstructure="diamond")
174+
calc = UPETCalculator(model="pet-mad-s", version="1.5.0")
175+
176+
forces_ensemble = calc.get_forces_ensemble(atoms)
177+
# same atoms, unchanged: served from cache, not recomputed
178+
assert calc.get_forces_ensemble(atoms) is forces_ensemble
179+
180+
# requesting stress next reuses the cached forces instead of discarding them
181+
calc.get_stress_ensemble(atoms)
182+
assert calc.get_forces_ensemble(atoms) is forces_ensemble
183+
184+
# moving the atoms invalidates the cache
185+
atoms.positions[0, 0] += 0.1
186+
forces_ensemble_moved = calc.get_forces_ensemble(atoms)
187+
assert forces_ensemble_moved is not forces_ensemble
188+
assert not np.allclose(forces_ensemble_moved, forces_ensemble)
189+
190+
171191
def test_forces_method_defaults():
172192
"""Test that the default method depends on the non_conservative flag."""
173193
atoms = bulk("Si", cubic=True, a=5.43, crystalstructure="diamond")

0 commit comments

Comments
 (0)