From 93d2501f250373ee42571d06691efe3066992416 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Thu, 14 May 2026 21:01:41 +1000 Subject: [PATCH] Invalidate caches on Mesh._deform_mesh to match mesh.adapt() hygiene MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a coordinate change, three mesh-level caches need to be marked stale so subsequent lookups recompute against the new geometry: - self._evaluation_hash + self._evaluation_interpolated_results (the legacy uw.function.evaluate fast-path cache) - self._dminterpolation_cache (DMInterpolation structures keyed on coord-hash; stores cell residency + reference-to-physical maps) - self._topology_version (a counter that downstream caches consult) mesh.adapt() (line 3792, 3850-3853) and _legacy_access (line 1848-1853) already invalidate these. _deform_mesh did not, leaving a gap where the same uw.function.evaluate query against the same coord array could hit a stale cache built on the pre-deform geometry. Empirically the gap was not load-bearing for the convection problem that motivated this audit (the bit-identical trajectory before and after this patch confirms the cache was rarely hit on stale entries in that specific path — trace-back queries use fresh coord arrays each step, so the (coord_hash, dofcount) key changes and the cache mostly misses). Land as a hygiene improvement: it brings _deform_mesh into line with the other mesh-modifying operations and protects against future failure modes where _deform_mesh is followed by an evaluate at the same coord array (e.g. user code that holds onto a coord array across deformations). No behaviour change for code that doesn't hit the caches. No new API surface. Underworld development team with AI support from Claude Code --- .../discretisation/discretisation_mesh.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/underworld3/discretisation/discretisation_mesh.py b/src/underworld3/discretisation/discretisation_mesh.py index bf3e4075..b66fe405 100644 --- a/src/underworld3/discretisation/discretisation_mesh.py +++ b/src/underworld3/discretisation/discretisation_mesh.py @@ -1810,6 +1810,19 @@ def _deform_mesh(self, new_coords: numpy.ndarray, verbose=False): if solver is not None and hasattr(solver, "is_setup"): solver.is_setup = False + # Invalidate caches whose contents become stale when mesh + # coordinates change. Matches the cache hygiene already + # performed by mesh.adapt() and _legacy_access. Without these, + # uw.function.evaluate (and any user code that keys lookups + # off _topology_version) can return values computed against + # the pre-deform mesh. + self._evaluation_hash = None + self._evaluation_interpolated_results = None + if hasattr(self, '_dminterpolation_cache'): + self._dminterpolation_cache.invalidate_all( + reason="mesh deformed") + self._topology_version += 1 + # Propagate coordinate changes to registered submeshes for submesh in self._registered_submeshes: submesh.sync_coordinates_from_parent()