Summary
The force_dry lookup appears in five places in the 2d shallow/bouss code, and they do
not agree with each other. qinit.f90 is off by one column in x; filpatch.f90 and
filval.f90 (both the shallow and the bouss copies) are off by one row in y. Since
all five compute the same thing from the same arrays, at most one convention can be right,
and in fact each site is correct on one axis and wrong on the other.
Affected: v5.14.0, and still present on master (11479f67, 2026-01-24).
The five call sites
src/2d/shallow/qinit.f90 (lines 44-51):
do i=1,mx
x = xlower + (i-0.5d0)*dx
ii = int((x - xlow_fdry + 1d-7) / dx_fdry)
do j=1,my
y = ylower + (j-0.5d0)*dy
jj = int((y - ylow_fdry + 1d-7) / dy_fdry)
jj = my_fdry - jj ! since index 1 corresponds to north edge
src/2d/shallow/filpatch.f90 (338-340), src/2d/shallow/filval.f90 (235-237), and the
identical src/2d/bouss/filpatch.f90 and src/2d/bouss/filval.f90:
ii = int((xcent_fine - xlow_fdry + 1d-7) / dx_fdry) + 1
jj = int((ycent_fine - ylow_fdry + 1d-7) / dy_fdry) + 1
jj = my_fdry - jj ! since index 1 corresponds to north edge
Note that qinit.f90 has no + 1 on either line, while the other four have + 1 on both.
Why both are wrong
force_dry is allocated (mx_fdry, my_fdry), i.e. 1-based, in
src/2d/shallow/qinit_module.f90:292, and row index 1 is the north row. These lookups only
run when the patch resolution matches the mask resolution (the ddxy test in qinit.f90,
use_force_dry_this_level elsewhere), so cell centers and mask cells are aligned.
For a cell center in mask column k, counted 1-based from the west:
int((x - xlow_fdry + 1d-7) / dx_fdry) == k - 1
so the correct column index is int(...) + 1. qinit.f90 omits it and reads column k-1,
the western neighbour.
For a cell center in mask row m, counted 1-based from the south, int(...) == m - 1. The
array is stored north row first, so the correct row index is
my_fdry - m + 1 == my_fdry - int(...)
which is what qinit.f90 computes. The other four compute my_fdry - (int(...) + 1),
i.e. one row too far.
So the correct pair, at all five sites, is:
ii = int((x - xlow_fdry + 1d-7) / dx_fdry) + 1
jj = my_fdry - int((y - ylow_fdry + 1d-7) / dy_fdry)
This does not depend on whether xlow_fdry/ylow_fdry are read as the corner of the mask
raster or as the center of its first cell: on an aligned grid the two conventions change the
quotient by exactly 0.5, and int() gives the same result for both.
Effect
The mask is applied one cell away from where it was computed, so cells that should start dry
start wet and vice versa. It is easy to miss because the shift is small and silent: the
out-of-range index at the edge (ii = 0 in qinit.f90, jj = 0 on the north row in
filpatch/filval) is filtered by the surrounding (ii>=1) .and. ... test, so there is no
error message and no out-of-bounds access, and the whole westernmost column (resp. the
northernmost row) is simply never forced dry.
Suggested fix
Apply the two lines above at all five sites.
Summary
The
force_drylookup appears in five places in the 2d shallow/bouss code, and they donot agree with each other.
qinit.f90is off by one column in x;filpatch.f90andfilval.f90(both theshallowand thebousscopies) are off by one row in y. Sinceall five compute the same thing from the same arrays, at most one convention can be right,
and in fact each site is correct on one axis and wrong on the other.
Affected: v5.14.0, and still present on master (
11479f67, 2026-01-24).The five call sites
src/2d/shallow/qinit.f90(lines 44-51):src/2d/shallow/filpatch.f90(338-340),src/2d/shallow/filval.f90(235-237), and theidentical
src/2d/bouss/filpatch.f90andsrc/2d/bouss/filval.f90:Note that
qinit.f90has no+ 1on either line, while the other four have+ 1on both.Why both are wrong
force_dryis allocated(mx_fdry, my_fdry), i.e. 1-based, insrc/2d/shallow/qinit_module.f90:292, and row index 1 is the north row. These lookups onlyrun when the patch resolution matches the mask resolution (the
ddxytest inqinit.f90,use_force_dry_this_levelelsewhere), so cell centers and mask cells are aligned.For a cell center in mask column
k, counted 1-based from the west:so the correct column index is
int(...) + 1.qinit.f90omits it and reads columnk-1,the western neighbour.
For a cell center in mask row
m, counted 1-based from the south,int(...) == m - 1. Thearray is stored north row first, so the correct row index is
which is what
qinit.f90computes. The other four computemy_fdry - (int(...) + 1),i.e. one row too far.
So the correct pair, at all five sites, is:
This does not depend on whether
xlow_fdry/ylow_fdryare read as the corner of the maskraster or as the center of its first cell: on an aligned grid the two conventions change the
quotient by exactly 0.5, and
int()gives the same result for both.Effect
The mask is applied one cell away from where it was computed, so cells that should start dry
start wet and vice versa. It is easy to miss because the shift is small and silent: the
out-of-range index at the edge (
ii = 0inqinit.f90,jj = 0on the north row infilpatch/filval) is filtered by the surrounding(ii>=1) .and. ...test, so there is noerror message and no out-of-bounds access, and the whole westernmost column (resp. the
northernmost row) is simply never forced dry.
Suggested fix
Apply the two lines above at all five sites.