Fix Integral/BdIntegral JIT cache collisions - #92
Merged
lmoresi merged 1 commit intoMar 25, 2026
Merged
Conversation
lmoresi
approved these changes
Mar 25, 2026
lmoresi
left a comment
Member
There was a problem hiding this comment.
Excellent - this works well.
I'll merge this and I have some additional changes to add in a new PR
lmoresi
added a commit
that referenced
this pull request
Mar 25, 2026
Refactor the JIT compilation pipeline to use a JITCallbackSet dataclass that groups the five PETSc callback lists (residual, bcs, jacobian, bd_residual, bd_jacobian) into a single structured container. This addresses the root cause of the cache-collision bug (PR #92) at an architectural level: the flat tuple hash that lost callback role information is replaced by a structured signature that preserves which slot each expression belongs to. Changes: - Add JITCallbackSet dataclass with flat(), signature(), map(), counts - Extract _structural_expand() as a module-level function (was inline) - Refactor getext() to accept JITCallbackSet (with backward compat) - Refactor _createext() to accept JITCallbackSet - Update all 6 call sites: 3 solvers (Scalar, Vector, Stokes) and 3 integrals (Integral, Integral._evaluate_integral, BdIntegral) - Include PR #92 regression tests (spherical shell cache collision) Incorporates the fix from PR #92 (gthyagi) which identified the bug and added the regression tests. Test results: 374 passed, 7 skipped, 1 xfailed (level_1 suite) Underworld development team with AI support from Claude Code
This was referenced Mar 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes an order-dependent JIT callback bug in
uw.maths.Integral(...)anduw.maths.BdIntegral(...).The failure showed up most clearly on spherical-shell meshes: evaluating a volume integral and a boundary integral on the same live mesh could return
0.0depending on which one was called first.Changes in this PR:
getext()petsc_u[...]Bug
The bug was not in the benchmark physics or in PETSc's spherical geometry handling. It was in Underworld's JIT extension cache.
Observed behaviour
On a spherical shell mesh, the following orders were inconsistent:
BdIntegral(...)only: correctBdIntegral(...)thenIntegral(...): the later volume integral could return0.0Integral(...)thenBdIntegral(...): the later boundary integral could return0.0This was reproducible with a minimal generic spherical-shell script using
fn=1.0and no Stokes solve.Root cause
underworld3.utilities._jitextension.getext()was building its cache key from a flattened tuple of expanded expressions.That loses the role of each expression:
So the same symbolic expression, for example
1.0, could be compiled in two incompatible callback contexts and still reuse the same cached module.In practice that meant a boundary callback build could reuse a residual-only extension, or vice versa, which then wired the wrong callback slot into PETSc.
While fixing that, a second issue also appeared:
primary_field_listcan be a generator in these call paths. If it is consumed while constructing the cache signature,_createext()no longer sees the primary fields. That causes mesh-variable boundary integrals to compile againstpetsc_a[...]instead ofpetsc_u[...].Fix
The fix is in
src/underworld3/utilities/_jitextension.py.1. Preserve callback role in the cache key
getext()now:That means identical expressions no longer collide across incompatible callback types.
2. Preserve the primary-field list for code generation
primary_field_listis converted to a tuple immediately.This prevents generator consumption during cache-key construction and ensures
_createext()still receives the full ordered set of primary fields for correctpetsc_u[...]mapping.Tests
Added two regression tests in
tests/test_0502_boundary_integrals.py:test_spherical_bd_then_integral_does_not_poison_volume_path()test_spherical_integral_then_bd_does_not_poison_boundary_path()These use a spherical shell mesh and assert that:
BdIntegral(mesh, 1.0, "Lower")remains nonzero before and after a volume integralIntegral(mesh, 1.0)remains nonzero after a boundary integralI also reran the full boundary-integral regression module after the fix:
cd /Users/tgol0006/uw_folder/uw3_git_gthyagi_latest/underworld3 /Users/tgol0006/.pixi/bin/pixi run -e amr-dev pytest tests/test_0502_boundary_integrals.py -qResult:
21 passedWhy this matters
This is a core correctness issue in JIT callback dispatch. It is not limited to the Thieulot benchmark.
Any workflow that mixes
Integral(...)andBdIntegral(...)on the same live mesh can be affected when the same symbolic form appears in both paths. The spherical-shell case made it easy to reproduce, but the underlying bug lives in shared JIT callback caching.