Skip to content

FieldPlaneAveraging: do not read ghost cells across non-periodic boundaries - #2042

Open
hgopalan wants to merge 1 commit into
kynema:mainfrom
hgopalan:claude-plane-average-ghost
Open

hgopalan wants to merge 1 commit into
kynema:mainfrom
hgopalan:claude-plane-average-ghost

Conversation

@hgopalan

@hgopalan hgopalan commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #2041. The multi-level FieldPlaneAveraging (from #1792) interpolates a coarse cell to the center of each fine bin using its closest neighbor. Next to a non-periodic domain boundary that neighbor is the ghost cell, which the code treats as a value located at the domain face. This PR verifies what those ghost cells actually hold during an ABL run and makes the averages independent of them.

What the ghost cells hold

Checked with a temporary print in ABLStats::calc_averages on abl_godunov_static_refinement with the level-1 box covering half of the wall (amr.n_error_buf = 0), reading the level-0 velocity next to the wall in the coarse part of the domain:

when u(k=-1) u(k=0) u(k=1)
step 0 (physics post_init_actions, before the first fillpatch) 0 6.67 6.95
steps 1-3 (after the nodal projection's fillpatch) 6.2 6.6 6.9

So in the time loop the wall-model gradient written by apply_bc_funcs does not reach the averages: the fillpatch at the end of the nodal projection replaces it with the hoextrap value of the wall_model BC, which is an extrapolation to the ghost-cell center, not a value at the face. At initialization the ghost is still the unfilled initial value (zero here), and the first-cell bins of the fine average read it: at step 0 the fine average at half a fine cell was 5.1 against 6.1 one step later. With the default ABL.log_law_height (level-0 half cell) the errors of the two bins cancel exactly in the interpolated mean, so the initial friction velocity is unaffected; with another log-law height or ABL.stats_max_level = 1 it is not. Which value the ghost holds and where it sits therefore depends on the boundary condition (ext_dir: face value; hoextrap/foextrap: ghost center; wall models: a gradient until the next fill) and on the point in the step at which the average is taken.

Change

Across a non-periodic domain boundary the interpolation now uses the interior neighbor instead of the ghost cell (one-sided linear interpolation). In the periodic direction and inside the domain nothing changes. Cells whose center coincides with the bin center still get zero interpolation weight, but only to roundoff: in the first and last cells next to a non-periodic boundary, the roundoff difference between the bin center and the cell center used to multiply the ghost value and now multiplies the interior neighbor. Single-level cases can therefore change at roundoff too (see Regression tests below; abl_godunov happens to be bit-identical). Multi-level cases with a coarse level at a non-periodic boundary change only in the first and last bins; abl_godunov_static_refinement with the level-1 box over half of the wall agrees with main to roundoff after 10 steps (1e-15 relative), because with the default log-law height the two wall bins cancel in the wall function and ABLForcing samples the profile well inside the domain.

Regression tests

The CPU Release jobs compare against main with no tolerance, so they report these answer changes. On the macOS job the following tests differ from main, all at roundoff:

test levels max. absolute difference (velocity, temperature, p)
abl_godunov_mpl 1 2.3e-12 (relative 9e-14)
abl_godunov_mpl_amr 2 2.2e-12
abl_sampling 2 7.7e-12
abl_subvolume 2 2.4e-13
box_refinement 2 4.5e-13
udf_refinement 2 2.8e-13

The relative differences are at most 1e-12, except for fields close to zero (e.g. the level-1 vertical velocity, 1e-9 relative). Running abl_godunov_mpl locally with and without this change gives the same size of difference.

Test

FieldPlaneAveragingFineTest.test_linear_ignores_domain_ghosts: the linear-field test of #1792 with the ghost cells outside the domain set to 1e10. It fails on the current code and passes with this change. The existing tests, which fill the ghosts with the face value, still pass because the one-sided interpolation is exact for a linear field.

🤖 Generated with Claude Code

…daries

The multi-level plane average interpolates a coarse cell to the center of
each fine bin using its closest neighbor. Next to a non-periodic domain
boundary that neighbor was the ghost cell, treated as a value located at
the domain face. What the ghost holds depends on the boundary condition
(a face value for ext_dir, an extrapolation to the ghost center for
hoextrap/foextrap, a gradient for the wall models until the next fill)
and on whether it has been filled at all: at initialization the ABL
statistics read it before the first fillpatch, and it still held the
initial value.

Use the interior neighbor instead (one-sided linear interpolation) across
a non-periodic boundary. Cells at bin centers and periodic directions are
unchanged, so single-level averages are bit-identical.

A unit test fills the ghost cells outside the domain with 1e10 and checks
that the averages of a linear field are unaffected; it fails on the
previous code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant