Regression Tests

This page covers complete ERF regression cases. For focused GoogleTest cases, see Unit Testing.

ERF runs a set of CMake regression tests on every pull request. These tests use the AMReX version in the ERF submodule. Nightly GNU Make tests use an AMReX build configured for the nightly environment.

Results from the nightly CPU tests can be found here: CPU tests

Results from the nightly GPU tests can be found here: GPU tests

The following problems are currently tested in the CI:

Test

nx ny nz

xbc

ybc

zbc

Ext

Other

Bubble_Density_Current

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

moist bubble

AnelasticWallDiffusion_X/Y/Z

8 12 17

NoSlipWall

NoSlipWall

NoSlipWall

None

rotated stationary linear-theta wall-diffusion tests

CouetteFlow_x

32 4 16

Periodic

Periodic

NoSlipWall NoSlipWall

None

inhomogeneous bc at zhi (u = 2)

CouetteFlow_y

4 32 16

Periodic

Periodic

NoSlipWall NoSlipWall

None

inhomogeneous bc at zhi (v = 2)

CouetteFlow_ImplicitCF

4 4 32

Periodic

Periodic

NoSlipWall NoSlipWall

None

two levels: fine patch over the lower half; implicit vertical diffusion closed at the c/f bdy

MoistDiffusion_ImplicitCF

4 4 32

Periodic

Periodic

SlipWall SlipWall

None

as CouetteFlow_ImplicitCF, for the implicit moisture solve

DensityCurrent

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

+gravity

DensityCurrent_detJ2

256 4 64

Symmetry Outflow Outflow

Periodic

SlipWall SlipWall SlipWall

None

terrain_type = StaticFittedMesh uses zlevels detJ = 2 everywhere

DensityCurrent_detJ2_nosub

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

terrain_type = StaticFittedMesh uses zlevels detJ = 2 everywhere no substepping

DensityCurrent_detJ2_MT

256 4 64

Symmetry Outflow Outflow

Periodic

SlipWall SlipWall SlipWall

None

terrain_type = MovingFittedMesh uses zlevels detJ = 2 everywhere

EkmanSpiral

4 4 400

Periodic

Periodic

NoSlipWall SlipWall

Geo

+Coriolis +gravity

IsentropicVortexAdvecting

48 48 4

Periodic

Periodic

SlipWall SlipWall

None

IsentropicVortexStationary

48 48 4

Periodic

Periodic

SlipWall SlipWall

None

MSF_NoSub_IsentropicVortexAdv

48 48 4

Periodic

Periodic

SlipWall SlipWall

None

tests map factors without substepping

MSF_Sub_IsentropicVortexAdv

48 48 4

Periodic

Periodic

SlipWall SlipWall

None

tests map factors with substepping

PoiseuilleFlow_x

32 4 16

Periodic

Periodic

NoSlipWall NoSlipWall

GradP in x

PoiseuilleFlow_y

4 32 16

Periodic

Periodic

NoSlipWall NoSlipWall

GradP in y

RayleighDamping

64 4 64

Periodic

Periodic

SlipWall SlipWall

None

Rayleigh damping

ScalarAdvectionUniformU

64 64 4

Periodic

Periodic

SlipWall SlipWall

None

ScalarAdvectionShearedU

64 4 64

Periodic

Periodic

SlipWall SlipWall

None

ScalarAdvDiff_order2

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

advection + diffusion “Centered_2nd”

ScalarAdvDiff_order3

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

advection + diffusion “Upwind_3rd”

ScalarAdvDiff_order4

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

advection + diffusion “Centered_4th”

ScalarAdvDiff_order5

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

advection + diffusion “Upwind_5th”

ScalarAdvDiff_order6

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

advection + diffusion “Centered_6th”

ScalarDiffusionGaussian

16 16 16

Periodic

Periodic

SlipWall SlipWall

None

ScalarDiffusionSine

16 16 4

Periodic

Periodic

SlipWall SlipWall

None

TaylorGreenAdvecting

16 16 16

Periodic

Periodic

SlipWall SlipWall

None

TaylorGreenAdvectingDiffusing

16 16 16

Periodic

Periodic

SlipWall SlipWall

None

ParticleAdvect_AMR1_box

128 4 32

Inflow Outflow

Periodic

SlipWall SlipWall

None

particle advection, 1 AMR level static box tagging, partial z

ParticleAdvect_AMR1_pcount

128 4 32

Inflow Outflow

Periodic

SlipWall SlipWall

None

particle advection, 1 AMR level particle-count tagging

ParticleAdvect_AMR2_pcount

128 4 32

Inflow Outflow

Periodic

SlipWall SlipWall

None

particle advection, 2 AMR levels particle-count tagging

while the following tests are run nightly:

Test

nx ny nz

xbc

ybc

zbc

Ext

Other

ABL-Deardorff

64 64 64

Periodic

Periodic

NoSlipWall SlipWall

None

LES

ABL-Deardorff-OMP

64 64 64

Periodic

Periodic

NoSlipWall SlipWall

None

LES uses OpenMP

ABL-MOST

64 64 64

Periodic

Periodic

SurfLay SlipWall

None

LES with MOST bc

ABL-MOST-OMP

64 64 64

Periodic

Periodic

SurfLay SlipWall

None

LES with MOST bc uses OpenMP

ABL-MYNN

2 2 64

Periodic

Periodic

SurfLay SlipWall

None

MYNN2.5 Model

ABL-Smag

64 64 64

Periodic

Periodic

NoSlipWall SlipWall

None

LES

ABL-Smag-OMP

64 64 64

Periodic

Periodic

NoSlipWall SlipWall

None

LES uses OpenMP

Bomex

32 32 100

Periodic

Periodic

SurfLay SlipWall

None

Kessler_NoRain

Bubble_Kessler

100 4 100

SlipWall

Periodic

SlipWall SlipWall

None

Kessler

Bubble_Kessler_NoRain

200 4 100

SlipWall

Periodic

SlipWall SlipWall

None

Kessler_NoRain

DensityCurrent

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

+gravity

DensityCurrent-OMP

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

+gravity uses OpenMP

DensityCurrent_Terrain

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

+gravity terrain_type = StaticFittedMesh

DensityCurrent_Terrain-OMP

256 4 64

Symmetry Outflow Outflow

Periodic

SlipWall SlipWall SlipWall

None

+gravity terrain_type = StaticFittedMesh uses OpenMP

DensityCurrent_anelastic

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

+gravity anelastic

DensityCurrent_detJ2

256 4 64

Symmetry Outflow

Periodic

SlipWall SlipWall

None

+gravity uses z_levels

EkmanSpiral_custom

12 10 400

Periodic

Periodic

NoSlipWall SlipWall

Geo Cor

custom init

EkmanSpiral_ideal

12 10 400

Periodic

Periodic

NoSlipWall SlipWall

Geo Cor

init from ideal wrfinput file

EkmanSpiral_input_sounding

4 4 400

Periodic

Periodic

NoSlipWall SlipWall

Geo Cor

init from input_sounding

EkmanSpiral_restart

4 4 400

Periodic

Periodic

NoSlipWall SlipWall

Geo Cor

restart test

IsentropicVortexAdvecting

48 48 4

Periodic

Periodic

SlipWall SlipWall

None

IsentropicVortexStationary

48 48 4

Periodic

Periodic

SlipWall SlipWall

None

MetGrid

140 80 100

Outflow

Outflow

SurfLay SlipWall

None

init from metgrid file

MovingTerrain_nosub

40 8 79

Periodic

Periodic

SlipWall SlipWall

None

terrain_type = MovingFittedMesh

NoahMP_Ideal

4 4 32

Periodic

Periodic

SurfLay SlipWall

None

Noah-MP land model, init from input_sounding

ParticlesOverWoA

256 8 64

Inflow Outflow

Periodic

SlipWall SlipWall

None

particle advection

ScalarAdvecDiffDoubleDen

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

Density = 2

ScalarAdvecDiffUniformU

32 32 32

Periodic

Periodic

SlipWall SlipWall

None

TaylorGreenAdvecting

64 64 64

Periodic

Periodic

SlipWall SlipWall

None

TaylorGreenAdvDiffDoubleDen

64 64 64

Periodic

Periodic

SlipWall SlipWall

None

Density = 2

TurbulentInflow

64 16 32

Inflow Outflow

Periodic

SurfLay SlipWall

None

LES

TurbulentInflow_anelastic

64 16 32

Inflow Outflow

Periodic

SurfLay SlipWall

None

LES

WPS_Test

200 200 176

wrfbdy wrfbdy

wrfbdy wrfbdy

NoSlipWall SlipWall

None

init from wrfinput

WPS_Test-OMP

200 200 176

wrfbdy wrfbdy

wrfbdy wrfbdy

NoSlipWall SlipWall

None

init from wrfinput

WPS_Test_restart

200 200 176

wrfbdy wrfbdy

wrfbdy wrfbdy

NoSlipWall SlipWall

None

init from wrfinput

WPS_Test-RRTMGP+NOAHMP

100 100 79

wrfbdy wrfbdy

wrfbdy wrfbdy

NoSlipWall SlipWall

None

init from wrfinput uses RRTMGP+NOAHMP

WPS_Test-RRTMGP+SLM

100 100 79

wrfbdy wrfbdy

wrfbdy wrfbdy

NoSlipWall SlipWall

None

init from wrfinput uses RRTMGP+SLM

WPS_Test-ML_Interp

100 100 79

wrfbdy wrfbdy

wrfbdy wrfbdy

NoSlipWall SlipWall

None

init from wrfinput; max_level=1

More details about the CI tests are given below.

Scalar Advection by Uniform Flow in XY Plane

This tests scalar advection with periodic boundaries in the lateral directions and slip walls at low and high z.

Test Location: Tests/test_files/ScalarAdvectionUniformU

Problem Location: Exec/RegTests/ScalarAdvDiff

27 X-Y slice of a 2-d cylindrical blob in a uniform velocity field (10,5,0)

a2

b2

Scalar concentration at t=0.

Scalar concentration at 20 steps.

Scalar Advection by Sheared Flow

This tests scalar advection with periodic boundaries in the lateral directions and slip walls at low and high z.

Test Location: Tests/test_files/ScalarAdvectionShearedU

Problem Location: Exec/RegTests/ScalarAdvDiff

28 X-Z slice of a 2-d cylindrical blob in a uniform shearing velocity field (8 log( (z+z0)/z0 ) / log ( (zref+z0)/z0 ) with z0 = 0.1 and zref = 80 in a triply periodic domain 8x8x8

a3

b3

Scalar concentration at t=0.

Scalar concentration at 80 steps

Scalar Diffusion: Sphere of Scalar

This tests scalar diffusion with periodic boundaries in the lateral directions and slip walls at low and high z.

Test Location: Tests/test_files/ScalarDiffusionGaussian

Problem Location: Exec/RegTests/ScalarAdvDiff

29 Diffusion of a spherical blob of scalar

a5

b5

Scalar concentration at t=0.

Scalar concentration at 20 steps (t = 0.01).

Scalar Diffusion: Sinusoidal Variation of Scalar

This tests scalar diffusion with periodic boundaries in the lateral directions and slip walls at low and high z.

Test Location: Tests/test_files/ScalarDiffusionSine

Problem Location: Exec/RegTests/ScalarAdvDiff

30 Diffusion of a scalar initialized as sin(x)

a6

b6

Scalar concentration at t=0.

Scalar concentration at 20 steps (t = 0.2).

Scalar Advection/Diffusion by Uniform Flow With Different Spatial Orders

This tests scalar advection and diffusion with periodic boundaries in the lateral directions and slip walls at low and high z.

Test Location (for 2nd order): Tests/test_files/ScalarAdvDiff_order2

Problem Location: Exec/RegTests/ScalarAdvDiff

31 Advection and diffusion of a spherical blob in a uniform velocity field (100,0,0)

a7

b7

Scalar concentration at t=0.

Scalar concentration at 20 steps (t = 0.01).

Rayleigh Damping

This tests Rayleigh damping. The problem is initialized as in the shear flow case, then Rayleigh damping is applied with a target mean profile of (2,1,0).

Test Location: Tests/test_files/RayleighDamping

Problem Location: Exec/RegTests/ScalarAdvDiff

Isentropic Vortex: Stationary

This tests advection of an isentropic vortex with triply periodic boundaries.

Test Location: Tests/test_files/IsentropicVortexStationary

Problem Location: Exec/RegTests/IsentropicVortex

Isentropic Vortex: Advecting

This tests advection of an isentropic vortex with triply periodic boundaries.

Test Location: Tests/test_files/IsentropicVortexAdvecting

Problem Location: Exec/RegTests/IsentropicVortex

Taylor Green Vortex: Advection

This tests advection and diffusion with triply periodic boundaries.

Test Location: Tests/test_files/TaylorGreenAdvecting

Problem Location: Exec/RegTests/TaylorGreenVortex

Taylor Green Vortex: Advection and Diffusion

This tests advection and diffusion with triply periodic boundaries.

Test Location: Tests/test_files/TaylorGreenAdvectingDiffusing

Problem Location: Exec/RegTests/TaylorGreenVortex

32 Scalar concentration

a8

b8

Flow field at t=0.

Flow field at 10 steps (t = 1.6).

Couette Flow (x-direction)

This tests Couette flow in a channel. The domain is periodic in the x- and y-directions, and has NoSlipWall bc’s on the low-z and high-z faces. At the high-z boundary the velocity is specified to be \(U = (2,0,0)\). The steady solution for this problem is \(U = (z/8,0,0)\) in the domain which is 16 units high in z.

Test Location: Tests/test_files/CouetteFlow_x

Problem Location: Exec/RegTests/Couette_Poiseuille

Couette Flow (y-direction)

This tests Couette flow in a channel. The domain is periodic in the x- and y-directions, and has NoSlipWall bc’s on the low-z and high-z faces. At the high-z boundary the velocity is specified to be \(U = (0,2,0)\). The steady solution for this problem is \(U = (0,z/8,0)\) in the domain which is 16 units high in z.

Test Location: Tests/test_files/CouetteFlow_y

Problem Location: Exec/RegTests/Couette_Poiseuille

Couette Flow with a coarse/fine boundary in z

This is Couette flow started from rest, driven by the lid at the high-z boundary (\(U = (2,0,0)\)), with a static refined patch that covers only the lower half of the column (\(0 \le z \le 8\) of 16). The diffusion front from the lid therefore crosses a coarse/fine boundary in z. The vertical implicit solve is used in all three RK stages (erf.vert_implicit_fac = 1) with a fine-level diffusion number of about 6, and each fine column is closed at the coarse/fine boundary with the coarse data at the stage time. The test compares the plotfile after 50 coarse steps against a gold file.

Test Location: Tests/test_files/CouetteFlow_ImplicitCF

Problem Location: Exec/RegTests/Couette_Poiseuille

Moisture diffusion with a coarse/fine boundary in z

The companion of the test above for the implicit moisture solve, which is done after the acoustic substeps (ERF_ImplicitPost.H) rather than before them. A moist layer (\(q_v = 10\) g/kg above \(z = 12\) of 16, dry below) at rest diffuses down (erf.alpha_C = 4) through a static refined patch over the lower half of the column, with the vertical implicit solve in all three RK stages and two-way coupling. Each fine column is closed at the coarse/fine boundary with the coarse data at the stage time. The test compares the plotfile after 200 coarse steps against a gold file.

Test Location: Tests/test_files/MoistDiffusion_ImplicitCF

Poiseuille Flow (x-direction)

This tests Poiseuille flow in a channel. The domain is periodic in the x- and y-directions, and has NoSlipWall bc’s on the low-z and high-z faces. We initialize the solution with the steady parabolic profile \(U = (1-z^2,0,0)\) in the domain which runs from -1. to 1. in z. The viscosity is specified to be 0.1 and the imposed pressure gradient is \(Gp = (-0.2,0,0)\).

Test Location: Tests/test_files/PoiseuilleFlow_x

Problem Location: Exec/RegTests/Couette_Poiseuille

Poiseuille Flow (y-direction)

This tests Poiseuille flow in a channel. The domain is periodic in the x- and y-directions, and has NoSlipWall bc’s on the low-z and high-z faces. We initialize the solution with the steady parabolic profile \(U = (0,1-z^2,0)\) in the domain which runs from -1. to 1. in z. The viscosity is specified to be 0.1 and the imposed pressure gradient is \(Gp = (0,-0.2,0)\).

Test Location: Tests/test_files/PoiseuilleFlow_y

Problem Location: Exec/RegTests/Couette_Poiseuille

Nonlinear Density Current

The density current problem tests the effects of gravity and the behavior at a slip wall.

See Verification for more information.

Test Location: Tests/test_files/DensityCurrent

Problem Location: Exec/CanonicalTests/DensityCurrent

Canonical RANS

Five cases exercise the one-equation \(k\) RANS closure (see One-equation RANS closure) on flat ground (neutral, stable and convective boundary layers) and on terrain-fitted meshes (a 2D ridge and a 3D hill), each for 40 steps. Every entry then runs the case’s Python check script, which reads the plotfile with a standard-library AMReX reader, averages or samples the fields, and compares numbers against stated targets with tolerances: wall distance against the exact distance to the terrain, length scale against its bounds, the wall value of \(k\) against \(u_*^2 / (c_\mu^0)^2\), dissipation against AL01 Eq. 19, and, in the longer physics runs documented in each case’s README, the log law, GABLS1 depths and jets, the convective heat budget and the hill-top speed-up. The script’s exit code is the verdict; a clean exit alone never passes a test. The _Poisson variants run the terrain cases with the Poisson wall distance instead of the terrain height.

Test names: RANS_Neutral_ABL_Flat, RANS_Stable_ABL_Flat, RANS_Convective_ABL_Flat, RANS_Neutral_Hill_2D (and _Poisson), RANS_Flat_Fitted_2D (and _Poisson), RANS_Neutral_Hill_3D (and _Poisson); label rans. RANS_WallDist_RaisedBase and RANS_WallDist_RaisedBase_StretchedZSplit run the neutral flat case for one step with the bottom at 300 m, on a constant-dz mesh and on a stretched mesh whose boxes are split in z, and check that the wall distance is the height above the bottom wall. RANS_Neutral_Hill_2D_Realizability runs the ridge twice, without and with the realizability cap of the closure (erf.rans_realizability), and pins the capped viscosity and diffusivity to their closed form cell by cell (check_realizability.py). The flat cases run with the MLMG projection (erf.use_fft=false); the terrain-fitted cases need the FFT-preconditioned projection and are registered only when the build enables FFT (ERF_ENABLE_FFT).

RANS_Checks_SelfTest tests the check scripts’ own verdict logic rather than any physics: it states, for each kind of comparison the shared rans_checks.py offers, what the check must decide for values inside and just outside the stated tolerance or band, and fails when a check disagrees. It also calls check_implicit_explicit_ke.py with malformed arguments (a missing or non-numeric --tol, too few plotfiles), each of which must print the usage and exit with status 2 rather than fail with a traceback. It runs no ERF executable; labels rans and unit. It is registered only when CMake finds a Python 3 interpreter, so a configuration without one simply does not have the test rather than failing the unit stage.

Problem Location: Exec/CanonicalTests/Canonical_RANS

Restart parity

MoistBubble_Kessler_Restart (MPI builds, not Windows) runs the moist bubble deck with Kessler rain (erf.moisture_model=Kessler, the rain fields in the plotfile) straight to step 8, again to a checkpoint at step 4, and from that checkpoint to step 8, and requires the two plotfiles at step 8 to be identical (Tests/RunRestartParity.cmake, no gold file; label restart-parity). Every run has a time limit of its own, so a restart whose first step never finishes fails with a message. Until September 2026 the restart path handed the microphysics its minimum cell height only on terrain-fitted meshes; on a constant-dz mesh the sedimentation substep count of the first restarted step was computed from an uninitialised value and the step never finished. Only the schemes that size their sedimentation substeps from that height are affected, namely Kessler (ERF_Kessler.cpp) and SAM (ERF_PrecipFall.cpp, ERF_IceFall.cpp); Morrison, WSM6 and WDM6 store the minimum cell height but never read it.

Test Location: Tests/test_files/MoistBubble_Kessler_Restart

IBSEB_Cube_Restart, IBSEB_RefinedLevels_Restart, ImmersedTerrain_Hill_Restart and ImmersedTerrain_Hill_TwoLevel_Restart (MPI builds, not Windows) do the same for immersed forcing: the IBSEB_Cube deck through a checkpoint at step 17 to step 40, with the velocities added to its plotfile; the two-level IBSEB_RefinedLevels deck through step 7 to step 20; and the TerrainHill deck with erf.terrain_type = ImmersedForcing through step 7 to step 20, on one level and on the deck’s two. All but one compare with zero tolerance. IBSEB_RefinedLevels_Restart compares to 1e-8 relative: the restart leg writes a plotfile at the restart step and the straight leg does not, and on two levels writing a plotfile changes the solution at round-off (erf-model/ERF#4224; 5e-15 relative in theta without buildings, 1.7e-10 relative in w in this deck by step 20); with plotfiles at the same steps in every leg that restart is bit-exact too.

A restart rebuilds the immersed blanking (terrain_IB_mask) rather than reading it from the checkpoint, and erf.ibseb builds its faces from it. Until October 2026 the restart path built it without clearing the almost-fluid cells (solid fraction below eb2.small_volfrac) as a fresh start does, so the restarted run applied the wall law in cells the straight run leaves alone: 0.2 m/s in u on one level and 0.85 m/s on the refined level of the hill, and 6e-5 m/s in u and 8e-6 K in the face skin temperatures of the cube (the faces themselves were the same; they take the cells with a blanking of at least 0.5). A fresh start, a regrid and a restart now build the blanking with the same function, ERF::make_terrain_blanking.

Test Location: Tests/test_files/IBSEB_Cube, Tests/test_files/IBSEB_RefinedLevels, Tests/test_files/TerrainHill

Closure box, rank and tiling parity

The Closure_BoxParity_* tests (MPI builds, not Windows; label box-parity) run the unstable, perturbed ABL deck of ABL_MRF_Tiling twice: on one box, on one rank, without tiling, and on four 16 x 16 x 32 boxes on two ranks with 8 x 8 tiles. The plotfiles after 10 steps must agree to a relative tolerance of 1e-9 (Tests/RunBoxParity.cmake, no gold file). The entries choose the physics on the command line: the Deardorff closure, once with erf.vert_implicit = false and once with the implicit vertical solve that ERF uses by default; the k-eqn closure with its PBL-height length cap; the MYNN25, MYNNEDMF, MYJ and native SHOC PBL schemes; Kessler microphysics on a moist sounding; and Smagorinsky on a stretched mesh whose levels are chosen so that they sum to the top of the domain. With the FFT build, two more run the anelastic MidPoint integrator with the Deardorff closure and with Kessler microphysics. Until September 2026 the anelastic integrator copied the projected momentum into the fluxes of the slow scalars tile by tile inside the loop that advects them, so the turbulent kinetic energy, moisture and passive scalars of anelastic runs depended on the tile size. The boxes are never split in z, so the column solves apply. The moist sounding is supersaturated below 150 m so that Kessler condenses, autoconverts and sediments within the ten steps of the run; otherwise qc and qp would be compared as zero against zero. MYNNEDMF computed its diffusivities on a box grown by one cell in the vertical, so its vertical-derivative stencil reached two cells outside the domain; it now uses the valid box, as MYNN25 does. The ghost values it computed were discarded in any case, since ComputeTurbulentViscosity refills every eddy-viscosity ghost cell after the scheme returns, and dropping them leaves the answer bit for bit unchanged.

Test Location: Tests/test_files/Closure_BoxParity

Conservation across coarse/fine boundaries

The Conservation_* tests (label conservation, no gold file) run a two-level deck with two-way coupling in a closed domain: slip walls in z, periodic in x and y. They require the composite integral of a scalar to be the same at the start and the end of the run (Tests/RunConservation.cmake). The integral is the one ERF prints every step with erf.sum_interval = 1 (RHO SCALAR SL/ML or RHO QTOTAL SL/ML, see erf.sum_interval in Inputs). It weights each cell by \(\det J/(m_x m_y)\) and masks the covered cells, so it is the quantity the discretization conserves on any mesh and with any refinement ratio. The two values must agree to 12 significant digits in double precision and to 5 in single precision.

That holds only if the reflux at the coarse/fine faces includes the diffusive fluxes, both the explicit ones and the implicit part of the vertical ones (erf-model/ERF#4323). Before that fix every one of these tests failed: the moisture integral changed by about 1e-3 relative, and the scalar integral by 1e-4 to 3e-4 (2e-8 with Smagorinsky diffusion alone).

The four Conservation_MoistDiffusion_* tests run 100 steps of the MoistDiffusion_ImplicitCF deck, whose patch has a coarse/fine face normal to z, and check the total moisture. Implicit, ImplicitStretched and ImplicitTerrain diffuse it with the implicit vertical solve in every RK stage, on a flat mesh, a stretched mesh (erf.initial_dz = 0.4) and a terrain-fitted one (erf.terrain_type = StaticFittedMesh). Explicit diffuses it explicitly (erf.vert_implicit = false, erf.alpha_C = 0.4).

The Conservation_ScalarDiffusion_* and Conservation_ScalarAdvection_* tests run the ScalarDiffusion_Reflux deck: a scalar blob diffusing for 50 steps through a patch over the middle half of the domain in x and y and the lower half in z, so through faces normal to all three directions. Flat runs it as it stands. Terrain uses a terrain-fitted mesh over flat terrain and Hill one over a hill (a radial Witch of Agnesi, 0.25 high, half-width 0.15), Stretched a stretched mesh (erf.initial_dz = 0.025), MapFactor map factors (erf.test_mapfactor = true), and Ratio221 2 x 2 x 1 refinement. Anelastic uses the anelastic MidPoint integrator, which refluxes its second RK stage rather than the third. AnelasticRK2 uses the anelastic RK2 integrator, whose update averages its two stages, so it refluxes half the fluxes of each; Conservation_ScalarAdvection_AnelasticRK2 runs it with the scalar advected (prob.U_0 = 10, prob.V_0 = 5) and not diffused. Until erf-model/ERF#4331 was fixed the RK2 integrator refluxed its second stage alone, at full weight, and both RK2 tests failed: the scalar integral changed by 2.6e-6 relative with diffusion and 1.4e-5 with advection. Smagorinsky advects the scalar with a sheared wind and diffuses it with Smagorinsky diffusion alone.

Until erf-model/ERF#4330 was fixed the hill test failed even on one level: the scalar diffused through the bottom wall where the terrain slopes (5.9e-5 relative in this run).

A Deardorff or k-equation TKE always dissipates, so the implicit diffusion of TKE is not tested here. It goes through the same functions as that of moisture.

EB runs do not reflux diffusive fluxes, and ERF warns when two-way coupling is used with EB and diffusion.

Test Location: Tests/test_files/MoistDiffusion_ImplicitCF, Tests/test_files/ScalarDiffusion_Reflux

Terrain Poisson operator at Neumann faces

The TerrainPoisson_* tests (MPI builds, not Windows) check the anelastic projection on a terrain-fitted mesh where the terrain slopes at a Neumann face of the region being solved (Neumann faces on terrain-fitted meshes). The deck is a 100 m hill on 32 x 32 x 16 cells with one refined level (ratio 2 2 1) whose four coarse/fine faces lie on the hill’s flanks; run on one level inside slip walls, the hill’s slopes reach the walls instead. ERF prints the divergence before and after every solve (erf.mg_v = 1), and Tests/check_terrain_poisson_divergence.py requires each solve on the checked level to leave no more than the constant that a singular solve cannot remove, within the solver tolerance. TerrainPoisson_TwoLevelHill checks the refined level and TerrainPoisson_SlopingWalls the walled level: each leaves at most 1e-9, where the operator before erf-model/ERF#4279 (no zero-flux faces; development at 905623352) left up to 1.6e-2 and 7.6e-4. TerrainPoisson_TwoPatches refines two separate patches on the flanks, so that level 1 is solved as two regions (development stopped there with a segmentation fault). TerrainPoisson_MapFactors and TerrainPoisson_MapFactors_Walls run the two decks with erf.test_mapfactor (map factors 0.5 and 0.25), where the projection used to leave 75 % of the divergence after every solve. TerrainPoisson_Hill_BoxParity (one box on one rank against split boxes) and TerrainPoisson_Hill_Restart check the decomposition and a restart; TerrainPoisson_DivergenceChecker_SelfTest feeds the checker made-up logs on both sides of its limit. These runs need the FFT build; the self-test does not.

The same checks run with erf.terrain_poisson_solver = mlmg and gmres_mlmg (Multigrid on terrain-fitted meshes), which need no FFT and so run in every MPI build: TerrainPoisson_TwoLevelHill_{mlmg,gmres_mlmg}, TerrainPoisson_SlopingWalls_{mlmg,gmres_mlmg}, TerrainPoisson_TwoPatches_mlmg, TerrainPoisson_MapFactors_mlmg, TerrainPoisson_OutflowFace_{mlmg,gmres_mlmg} (an Outflow face, so a non-singular solve), TerrainPoisson_OneCellStrip_{mlmg,gmres_mlmg} (one cell wide between a wall and an Outflow face over sloping terrain) and TerrainPoisson_PeriodicApart_mlmg (two patches at the two ends of a periodic direction that do not face each other) (at a solver tolerance of 1e-10, leaving at most 1.2e-11 on the checked level); TerrainPoisson_Hill_BoxParity_MLMG and TerrainPoisson_Hill_Restart_MLMG; and three start-up or first-solve aborts (an unknown solver, a refined region that is not a rectangle, refined patches meeting across a periodic boundary), and in double precision TerrainPoisson_Abort_MLMGNotConverged (MLMG stops with a message naming the inputs to change). In FFT builds TerrainPoisson_Hill_SolverAgreement, TerrainPoisson_SlopingWalls_SolverAgreement and TerrainPoisson_OutflowFace_SolverAgreement compare FFT-GMRES on one box with MLMG on split boxes, to 1e-8.

Test Location: Tests/test_files/TerrainPoisson_Hill

Station time series

StationSampling_BoxParity and StationSampling_Restart cover the station time series written by erf.station_names (see Inputs). A station value is an interpolation from whichever level and whichever box happens to cover the point, so the two things most likely to break it are a change of decomposition and a restart, and both are checked against the run that does it in one piece. Beyond the plotfile comparison every parity test makes, each compares Output_Stations/Center.dat line by line, to the ten significant digits the series prints rather than the six a data log prints (DATALOG_SIGDIGITS in Tests/RunBoxParity.cmake and Tests/RunRestartParity.cmake). Center.dat is the series compared because it is the one that varies; the stations in the still air away from the bubble would compare a constant against a constant. The restart comparison strips the comment lines first, since the restarted run marks the seam with a comment the straight run does not have.

The deck is the Straka density current refined over the lower middle of the domain, with four stations chosen so that the run touches every path the sampler has: Center, two locations inside the refined region with two heights each, so the values come from level 1 and the vertical interpolation runs; Edge, inside the outer half cell of the non-periodic x boundary, where the horizontal stencil collapses onto the edge cell; Wrap, inside the outer half cell of the periodic y boundary, where the stencil reaches across the periodic image; and Surface, a 2D diagnostic, which has no height and is filled by the 2D plotfile path rather than the 3D one. The refined box stops halfway up the domain, so the deck also pins down the level test: a level supplies a station when it covers the column from the bottom of the domain up through the cells the vertical interpolation reads, not when it covers the whole column, and with erf.v = 1 the sampler prints the level it chose for each station. The deck lowers erf.station_buffer_steps to 2, well below its default of 100, so that a ten-step run exercises the flush path and, on the restart, the header check that fires with the first flush of the restarted run.

Test Location: Tests/test_files/StationSampling

Station output does not change the answer

The two StationSampling_AnswerParity* tests (label option-parity) hold the sampler to the claim made in Inputs, that turning station output on does not change the solution. Each runs one deck twice, once with the stations off and once with them on, and requires the plotfile to be identical bit for bit – --rel_tol 0 --abs_tol 0, not the tolerances the gold-file tests use, because a diagnostic that moves the answer at all is a bug rather than a tolerance question (Tests/RunOptionParity.cmake, no gold file). Both legs run on the same number of ranks with the same decomposition, so anything that survives is the sampler’s own doing. Each names a file the “on” leg must write and the “off” leg must not, so a misspelled option cannot pass as agreement, and the harness refuses two legs given the same options.

The claim is not free, which is why it is tested. The sampler asks BuildPlot3DScratch not to average the microphysics state down (sync_solution = false), since that call modifies the coarse solution. What it still does at every sampled step is fillpatch the state on every level up to the highest one a station is on, re-point the qmoist pointers on every level, and fill the requested variables over whole levels. Two decks cover the paths that could break:

  • StationSampling_AnswerParity on the StationSampling deck – two levels, dry. The one of the two whose station resolves to level 1, so it is the case that exercises FillPatchFineLevel.

  • StationSampling_AnswerParity_MOST on ABL_MOST – the surface layer. u_star and t_star are 2D diagnostics of the MOST path, so the “on” leg reads what the surface layer computed as well as the 3D state.

Two gaps are left open. The first is the configuration the sync_solution = false argument exists for: Lagrangian microphysics on two levels with CouplingType::TwoWay, where BuildPlot3DScratch would otherwise average the microphysics state down onto the coarse solution. No answer-parity test runs it, so that argument is held by reading rather than by measurement.

The second is a run driven by time-dependent lateral boundary data, the remaining case where an extra fill at t_new could in principle matter. There is no nc_bdy_file fixture under Tests/test_files, and the decks that read one (Exec/RegTests/WPS_Test, Exec/RegTests/MetGrid, the Katrina inputs under Exec/CanonicalTests/Hurricanes) need NetCDF input that CI does not have. The argument that it is safe is that the boundary path reaches ReadBndryPlanes::interp_in_time, which memoizes on the requested time and is otherwise a pure function of it, so a second fill at t_new re-derives what the step already wrote rather than consuming a read. That is an argument, not a measurement.

Ekman Spiral

The Ekman spiral problem tests the computation of the stress term internally and at no-slip walls, as well as Coriolis and geostrophic forcing.

See Verification for more information.

Test Location: Tests/test_files/EkmanSpiral

Problem Location: Exec/CanonicalTests/EkmanSpiral