ApplyPBLHSmoothing: give the stencil the halo it reads, and cover it with a tiling parity test - #3992
Merged
Merged
Conversation
…with a tiling parity test Closes #3978. ApplyPBLHSmoothing applies a 5-point stencil to the diagnosed PBL height, so every cell it writes depends on a column one further out. It looped over the whole planar work box and clamped only at the domain edge, so at any other edge of that box it read one column past the end of the FArrayBox. The array it reads is allocated on PerpendicularBox<ZDir>(mfi.growntilebox( IntVect(1,1,0))), and growntilebox() is the trap: it does not grow at an interior tile edge, because the grown tile boxes have to keep partitioning the grown box. A tile in the middle of a box therefore has no halo at all, and the stencil reads outside the array on every one of its edges. With bound checks the run aborts; without them it mixes whatever is next in the arena into the PBL height. On the ABL_MRF_Tiling deck the tiled and untiled runs came out 24.5 m apart in Lturb (12%), 24% apart in Kmv and Khv, and 0.51 K apart in theta -- the corrupted PBL height feeds the whole K-profile. The caller now names the region it wants smoothed and hands over a work array carrying `passes` columns of halo, grown from mfi.tilebox() explicitly rather than through growntilebox(), and the PBLH passes fill that halo. Pass p writes the region grown by passes-1-p and reads the one grown by passes-p, so each pass consumes one column and the last lands exactly on the tile box. Every read is in bounds and the result no longer depends on how the domain is divided. An assert states the halo contract; it is what caught the growntilebox() trap. The stencil also now takes the domain periodicity. It clamped (Neumann) at every domain edge, but across a periodic edge the halo holds the wrapped columns and should be read: clamping folded an edge into a domain that has none. Both tiling decks are periodic in x and y. Growing the work box duplicates a little PBLH work in the tile overlaps, so it is done only when smoothing is on. With erf.enable_pblh_smoothing=false -- the default -- the work box is the gbx the kernels already used, and the generated code and the answers are unchanged. erf.pblh_smoothing_passes > 1 cannot be supported this way: the state and surface-layer arrays carry one ghost column, so there is no second column to consume. It now aborts naming the limit instead of silently returning a decomposition-dependent answer. Nothing in the tree sets the option -- no deck, test or doc mentions enable_pblh_smoothing. Supporting more passes exactly needs the PBLH diagnosis split into its own MFIter loop over a ghosted planar MultiFab with a FillBoundary before the K-profile loop, which is left out here. Also move the include guard's #endif to the end of ERF_PBLModels.H: ApplyPBLHSmoothing was defined outside the guard. Tests: ABL_MRF_Tiling_Smooth and ABL_YSUNew_Tiling_Smooth run the MRF deck with smoothing on through the tiling parity harness. MRF and YSUNew size and fill the halo in separate code, so both are registered. Both fail on the unfixed code with the differences quoted above and pass here, bit-identical. Verified with GNU 13.3, Release, ERF_ENABLE_MPI=ON, 2 ranks: - ctest -L regression: 110/110, no gold file changes - one box vs four boxes with smoothing on, no tiling: PLOTFILE AGREE, every norm exactly 0 (before: Lturb 7.9 m apart at 3 passes) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hgopalan
added a commit
to hgopalan/ERF
that referenced
this pull request
Sep 13, 2026
Development merged erf-model#3972 and erf-model#3992, which put the PBL passes on a grown tile work box; the IB-aware per-column arrays now live on that box too (merge commit), and this registers PBL_IBAware_MRF_Tiling and PBL_IBAware_YSUNew_Tiling in the tiling-parity harness: the 40 m cube under a heated surface layer with a capped mixed layer, so the PBL height differs between the roof columns and open ground. Tiled and untiled runs agree bitwise for both schemes. The solar header takes PI from ERF_NumericalConstants.H, as the constants split of erf-model#3994 asks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Closes #3978.
The bug
ApplyPBLHSmoothingapplies a 5-point stencil to the diagnosed PBL height, so every cell it writes depends on a column one further out. It looped over the whole planar work box and clamped only at the domain edge, so at every other edge of that box it read one column past the end of theFArrayBox.The array it reads is allocated on
PerpendicularBox<ZDir>(mfi.growntilebox(IntVect(1,1,0))), andgrowntilebox()is the trap: it does not grow at an interior tile edge, because the grown tile boxes have to keep partitioning the grown box. A tile in the middle of a box therefore has no halo at all, and the stencil reads outside the array on every one of its edges. WithAMReX_BOUND_CHECKthe run aborts; without it, whatever is next in the arena is mixed into the PBL height, which then feeds the entire K-profile.On the
ABL_MRF_Tilingdeck witherf.enable_pblh_smoothing=true, tiled vs untiled:Lturb(the PBL height) 24.5 m apart, 12% relative;Kmv/Khv~24%; and it propagates into theta and the velocities.The fix
The caller now names the region it wants smoothed and hands over a work array carrying
passescolumns of halo, grown frommfi.tilebox()explicitly rather than throughgrowntilebox(), with the PBLH passes filling that halo. Passpwrites the region grown bypasses-1-pand reads the one grown bypasses-p, so each pass consumes one column of halo and the last lands exactly on the tile box. Every read is in bounds and the result no longer depends on how the domain is divided into boxes or tiles. AnAMREX_ALWAYS_ASSERTstates the halo contract — it is what caught thegrowntilebox()trap in the first place.The stencil also now takes the domain periodicity. It clamped (Neumann) at every domain edge, but across a periodic edge the halo holds the wrapped columns and should be read directly; clamping folded an edge into a domain that has none. Both tiling decks are periodic in x and y.
Growing the work box duplicates a little PBLH work in the tile overlaps, so it is done only when smoothing is on. With
erf.enable_pblh_smoothing=false— the default — the work box is thegbxthe kernels already used, and the answers are unchanged.Multi-pass smoothing now errors instead of lying
erf.pblh_smoothing_passes > 1cannot be supported this way: the state and surface-layer arrays carry one ghost column, so there is no second column to consume. It now aborts naming the limit:instead of silently returning a decomposition-dependent answer. At 3 passes with no tiling at all, one box and four boxes currently differ by 7.9 m in
Lturband 7.6% inKmv.Nothing in the tree sets this option — no deck, test or doc mentions
enable_pblh_smoothing. Supporting more passes exactly needs the PBLH diagnosis split into its ownMFIterloop writing a ghosted planarMultiFab, aFillBoundary, then a second loop for the K-profile. That is a much larger change and is left out here; say the word if it is wanted.Also
The include guard's
#endifinERF_PBLModels.Hsat beforeApplyPBLHSmoothing, leaving the function outside the guard. It survives only because no translation unit includes the header twice. Moved to the end of the file.Tests
ABL_MRF_Tiling_SmoothandABL_YSUNew_Tiling_Smoothrun the MRF deck with smoothing on through the tiling parity harness added in #3972/#3991. MRF and YSUNew size and fill the halo in separate code, so both are registered rather than just one. Both fail on the unfixed code with the differences quoted above, and pass here bit-identical.GNU 13.3, Release,
ERF_ENABLE_MPI=ON, 2 ranks:ctest -L regression: 110/110, no gold file changesPLOTFILE AGREE, every norm exactly0🤖 Generated with Claude Code