Coupling to Noah-MP¶
Overview¶
The Noah-MP Land Surface Model (LSM) is integrated with ERF to facilitate interaction with the Noah-MP (Multi-Physics) land surface processes.
This documentation covers key components of this interface and its associated data structures and routines, which are implemented in C++ and Fortran, and provides details on initialization and management of data structures necessary for these processes.
Building and Running with Noah-MP¶
To build ERF with Noah-MP support, the NetCDF, NetCDF Fortran, and HDF5 libraries are
required. Furthermore, ERF_ENABLE_NOAHMP=ON must be specified with CMake builds or USE_NOAHMP=TRUE
and USE_NETCDF=TRUE must be specified with GNU Make. Once an executable has been generated, the
inputs file for the simulation must specify Noah-MP as the land surface model type:
erf.land_surface_model = "NOAHMP"
Currently, Noah-MP may only be utilized for simulations that are initialized from a WRF input file
(erf.init_type = "WRFInput"). Additionally, two files are required to be in the run directory
for Noah-MP initialization: namelist.erf and NoahmpTable.TBL. Sample files are provided for
the namelist.erf and NoahmpTable.TBL.
To improve computational efficiency, the Noah-MP timestep, specified via NOAH_TIMESTEP
in the namelist.erf file, may be set larger than the ERF timestep to allow subcycling
in time. For example, if an 4s timestep is utilized for ERF and a 40s timestep is utilized for
Noah-MP, then Noah-MP will be updated every 10 steps.
Files Overview¶
Source/LandSurfaceModel/Noah-MP/ERF_NOAHMP.H: Contains the declaration of the NOAH class, which extends the NullSurf.
Source/LandSurfaceModel/Noah-MP/ERF_NOAHMP.cpp: Implements the initialization routine for the NOAH class and the per-step state exchange between ERF and Noah-MP.
The C++ ↔ Fortran coupling glue under Submodules/Noah-MP/drivers/erf
is no longer hand-written. Four files are generated at build time from a single
source of truth by tools/NoahmpIOCoupling.py (see
Generating the C++–Fortran Coupling Glue). Only the tracked *-mc
templates are edited by hand; the generated targets are git-ignored and recreated
on every build:
Template (tracked) |
Generated target (git-ignored) |
Role |
|---|---|---|
|
|
C++ |
|
|
C++ method implementations that forward into Fortran |
|
|
Fortran |
|
|
Fortran storage type holding the coupled members |
Submodules/Noah-MP/drivers/erf/NoahmpIO.H-mc: Defines the C++
NoahmpIO_typethat is used to interface with Noah-MP implementations, following a similar structure as the underlying Fortran interface (https://dx.doi.org/10.5065/ew8g-yr95). The@NoahmpIOCoupling:Source { ... }block inside this template is the canonical, ABI-ordered list of all coupled members.Submodules/Noah-MP/drivers/erf/tools/NoahmpIOCoupling.py: The stdlib-only code generator (any Python 3, no third-party libraries) that expands the
@NoahmpIOCoupling:Sourceblock into the four generated targets above. The build runs it automatically;--checkmode also drift-checks the generated files and audits the hand-writtenallocate()bounds.Submodules/Noah-MP/drivers/erf/NoahmpIOVarInitMod.F90: Hand-written (not generated) Fortran module that allocates the coupled arrays. The allocate bounds must match each member’s
@NoahmpIOCoupling:bounds_Ndannotation;NoahmpIOCoupling.py --checkaudits this.Submodules/Noah-MP/drivers/erf/NoahmpReadNamelistMod.F90: Hand-written Fortran module that reads
namelist.erf; it also holds the sentinel guards that let a C++-API value override a namelist default forscalar namelistmembers.Submodules/Noah-MP/drivers/erf/NoahmpWriteLandMod.F90: Hand-written Fortran module that controls which fields appear in the per-timestep NetCDF land output.
Submodules/Noah-MP/drivers/erf/NoahmpWriteRestartMod.F90 and NoahmpReadRestartMod.F90: Fortran modules that serialize and restore the full Noah-MP prognostic state (soil, snow, canopy, aquifer, albedo history, …) to/from a NetCDF restart file. They are exposed to C++ through the
WriteRestart/ReadRestartmethods ofNoahmpIO_typeand are used by ERF’s checkpoint/restart capability (see Checkpoint and Restart).Submodules/Noah-MP/drivers/erf/specs/: TOML specs that double as AI-assistant task prompts and human references for common coupling chores (
add-coupled-variable.toml,plot-land-output.toml, andimprove-coupling-codegen.toml).
NOAHMP Class¶
The NOAH class serves as the handler for initializing and managing the data structures required for NOAH-MP operations. It inherits from the NullSurf class. This class declares private variable NoahmpIO_type noahmpio, that is passed to NoahMP routines similar to the Fortran interface in the Noah-MP documentation (https://dx.doi.org/10.5065/ew8g-yr95)
NoahmpIO_type Structure¶
This structure is key for handling the input and output operations for NOAH-MP through C++ and Fortran interoperation. Contains various variables for domain, memory, and tile configuration. Also, contains arrays for geographic variables. At present this type exposes only a select set of variables. More variables should be exposed as needed by applications in ERF.
All coupled members are declared once, in ABI order, inside the
@NoahmpIOCoupling:Source { ... } block of
Submodules/Noah-MP/drivers/erf/NoahmpIO.H-mc. The member’s kind is inferred
from its C++ type and refined by a trailing @NoahmpIOCoupling: annotation:
int ids, ide, jds, jde; // dimensions/handles -- no annotation
noahmp_real DTBL; // @NoahmpIOCoupling:scalar
noahmp_real ZLVL = -9999.0; // @NoahmpIOCoupling:scalar
NoahArray2D<noahmp_real> XLAT; // @NoahmpIOCoupling:bounds_2d(xstart:xend, ystart:yend)
NoahArray3D<noahmp_real> T_PHY; // @NoahmpIOCoupling:bounds_3d(xstart:xend, kms:kme, ystart:yend)
From this one ordered list the generator re-derives the mirrored fi struct,
both constructors, the move constructor, NOAHMP_IO_FI_NUM_MEMBERS, the Fortran
bind(C) type, the coupled members of the storage type, the
C_LOC/C_F_POINTER wiring, and the C++ NoahArray extents. Because the
C++ struct and the Fortran bind(C) type are emitted from the same list in one
run, their member count, order, and types are identical by construction.
To expose a new variable you therefore add a single annotated line here and rebuild; the only remaining manual steps are the runtime wiring the generator does not own (array allocation, namelist guard, optional NetCDF output). See Generating the C++–Fortran Coupling Glue for the full procedure.
Note
Always use noahmp_real (C++) / c_kind_noahmp (Fortran) for coupled
floating-point members, never a hardcoded double/C_DOUBLE. Both are
selected by the DOUBLE_PREC build macro so the boundary precision tracks
amrex::Real. ERF builds Noah-MP in double precision; a run-time guard
(NoahmpIO_AssertAbi) aborts if the two compilers disagree.
Fortran Interoperability¶
The connection between C++ and Fortran is managed through NoahmpIO_fi. This module contains a mirroring of the C++ structure for NOAH-MP input-output operations.
Operations are invoked as methods on NoahmpIO_type; each forwards to its
*_fi Fortran implementation through the generated bind(C) glue. The main
ones are:
void ScalarInitDefault()/void VarInitDefault(): Initialize default scalar and array members ofNoahmpIO_typeand create the C pointers into the Fortran-owned storage.void InitMain(): Main initialization routine for the NOAH-MP operations.void ReadNamelist()/void ReadTable(): Readnamelist.erfandNoahmpTable.TBL.void ReadLandHeader()/void ReadLandMain(): Read the land input data.void DriverMain(): Advance the Noah-MP physics one land-surface step.void WriteLand(int filenum): Write the per-timestep NetCDF land output.void WriteRestart(const std::string& dir)/void ReadRestart(const std::string& dir): Serialize and restore the full prognostic state for checkpoint/restart (see Checkpoint and Restart).
For applications that manage one NoahmpIO_type per domain tile, the
NoahmpIO_vector container mirrors a Fortran allocatable array and is sized
exactly once via resize(size, level).
Usage¶
To use the NOAH class and associated functions, ensure the correct initialization sequence is followed within the simulation setup. The interplay between C++ and Fortran necessitates careful memory and data handling, which is crucial for ensuring performance and correctness in simulations. The interface is designed to mimic the Fortran interface described in the Noah-MP documentation, therefore similar practices should be followed.
Checkpoint and Restart¶
Noah-MP participates in ERF’s standard checkpoint/restart capability (see
Checkpoint / Restart for the general erf.check_file, erf.check_int,
erf.check_per, and amr.restart controls). No additional input options
are required to checkpoint or restart a Noah-MP simulation; whenever a
checkpoint is written, the land-surface state is written alongside the
atmospheric state, and it is read back automatically on restart.
A Noah-MP restart is bitwise reproducible: restarting from a checkpoint reproduces the trajectory of an equivalent cold-start run exactly. Achieving this requires persisting two pieces of state in addition to the regular ERF MultiFab data:
The land-surface substep counter (Noah-MP’s
itimestep). When the Noah-MP timestep is larger than the ERF timestep (subcycling viaNOAH_TIMESTEP; see Building and Running with Noah-MP), the counter determines when Noah-MP fires relative to the ERF steps. It is written to<chkfile>/lsm_step(one value per AMR level) and restored on restart so that the firing schedule, and hence the LSM-to-atmosphere flux timing, is preserved.The full Noah-MP prognostic state. The complete land state — soil temperature and moisture, the snowpack (including the active snow layers and the layer count), canopy and vegetation variables, the aquifer, albedo history, phenology, and accumulators — is serialized at the model’s working precision to
<chkfile>/noahmp_restart/Level_<lev>.ncusingNoahmpWriteRestart/NoahmpReadRestart. Each local block writes its tile into the global-domain NetCDF file collectively.
On restart, ERF first cold-initializes the land state from the WRF input file
and tables (as it would for a fresh run) and then overwrites it with the
checkpointed state; Noah-MP’s per-step input transfer pulls the restored state
into the physics on the first Advance.
Note
These per-level files (lsm_step and noahmp_restart/) are written into
the checkpoint directory and are not AMReX MultiFabs. If you copy or archive a
checkpoint manually, make sure these are included.
Note
Legacy checkpoints. Checkpoints written before this capability was added
do not contain the lsm_step file or the noahmp_restart directory. ERF
detects their absence and falls back to the previous behavior — the substep
counter resets to zero and the Noah-MP state is cold-initialized from the WRF
input — printing a warning that the restarted land trajectory will differ from
a cold start. Such restarts are therefore not bitwise reproducible.
Generating the C++–Fortran Coupling Glue¶
The C++ ↔ Fortran ABI under Submodules/Noah-MP/drivers/erf is produced by a
deterministic, stdlib-only code generator,
tools/NoahmpIOCoupling.py — not by an LLM. The generator reads the single
@NoahmpIOCoupling:Source { ... } block in NoahmpIO.H-mc and expands every
@NoahmpIOCoupling:<region> marker in the four *-mc templates into the
corresponding generated target (see the table under Files Overview).
The generated targets (NoahmpIO.H, NoahmpIO.cpp, NoahmpIO_fi.F90,
NoahmpIOVarType.F90) are git-ignored and recreated by the build:
GNU Make runs the generator at parse time (before source discovery).
CMake runs it at configure time.
A fresh checkout therefore builds with no manual step. Two convenience targets are available for editing or CI:
# GNU Make (from Submodules/Noah-MP/drivers/erf)
make codegen # regenerate the four targets in place
make codegen-check # exit 1 if regeneration would change anything (drift check)
# CMake
cmake --build <dir> --target noahmp_codegen
cmake --build <dir> --target noahmp_codegen_check
codegen-check does double duty: it fails if the generated targets are out of
sync with the @NoahmpIOCoupling:Source block, and it audits the
hand-written allocate() bounds in NoahmpIOVarInitMod.F90 against each
array’s @NoahmpIOCoupling:bounds_Nd annotation. Run it in CI.
Adding a coupled variable¶
The full, authoritative procedure (and an AI-assistant prompt) lives in Submodules/Noah-MP/drivers/erf/specs/add-coupled-variable.toml. In short:
Declare it (boundary glue). Add ONE annotated line to the
@NoahmpIOCoupling:Sourceblock inNoahmpIO.H-mc, in the ABI position you want (order in the block = ABI order):noahmp_real RAINBL; // @NoahmpIOCoupling:scalar namelist "rainfall [mm]" NoahArray2D<noahmp_real> RAINLSM; // @NoahmpIOCoupling:bounds_2d(xstart:xend, ystart:yend) "lsm rain" NoahArray3D<noahmp_real> SMOIS; // @NoahmpIOCoupling:bounds_3d(xstart:xend, nsoil:nsoil, ystart:yend)
The generator infers the kind from the C++ type (
int/noahmp_real/NoahArray{2,3}D). Array bounds must be listed in Fortran order and every bound token must be a literal or another coupled member name (xstart,xend,kms,kme,nsoil,numrad, …) so C++ and Fortran resolve it to the same value. Scalars may addnamelistand an optional"doc"string; ints (dimensions/handles) need no annotation and may carry a C++ default (e.g.int numrad = 2;).Regenerate.
make codegen(orcmake --build ... --target noahmp_codegen); a plain build does this automatically. Usecodegen-checkto verify.Runtime wiring (manual, not generated). For a new array, add an
allocate()inNoahmpIOVarInitMod.F90with bounds matching the@NoahmpIOCoupling:bounds_Ndannotation (scalars are not allocated). For ascalar namelistvariable, add a sentinel guard inNoahmpReadNamelistMod.F90so a C++-API value wins over the namelist default.(Optional) NetCDF land output. To make the variable appear in the per-timestep land output, add it to
NoahmpWriteLandMod.F90following the existingTSK/SMOISpattern (seespecs/plot-land-output.toml).
To extend the generator itself — a new annotation kind, a new generated region, or
better diagnostics — see specs/improve-coupling-codegen.toml.
Generating the ERF driver with CodeScribe¶
The ERF-side C++ driver, Source/LandSurfaceModel/Noah-MP/ERF_NOAHMP.cpp and ERF_NOAHMP.H, is not covered by the macroprocessor above (it contains the GPU-aware, component-indexed state exchange rather than flat ABI plumbing). It can optionally be updated with CodeScribe (https://github.com/akashdhruv/CodeScribe), an LLM-based code-update tool.
Follow the instructions in the CodeScribe repository
to configure your LLM environment. You will need API access for your preferred
model (e.g., OpenAI, Argo, etc.). Tutorials are available at
https://github.com/akashdhruv/codescribe-tutorial.
The driver’s design specifications — the component-indexed field enums, the
GPU-aware state exchange, the lifecycle, and the contract a code-update must
respect — live as developer specs under
Source/LandSurfaceModel/Noah-MP/dev/ (start with dev/README.md):
code-scribe update ERF_NOAHMP.cpp ERF_NOAHMP.H \
-q "Write a natural language prompt with variable names, dimensions, etc." \
-m <openai|argo-gpt4o|...>
Note
CodeScribe is no longer used for the Submodules/Noah-MP/drivers/erf coupling
glue (NoahmpIO.H, NoahmpIO.cpp, NoahmpIO_fi.F90,
NoahmpIOVarType.F90). That glue is now generated deterministically by
tools/NoahmpIOCoupling.py from the @NoahmpIOCoupling:Source block; do not
hand-edit the generated targets. The previous manual
real(kind=kind_noahmp) → real(kind=C_DOUBLE) substitution is also
obsolete — coupled reals use noahmp_real / c_kind_noahmp, whose precision
is selected by the DOUBLE_PREC build flag.