wrfkit design notes¶
Scope boundary¶
wrfkit separates three concerns:
- Software environment โ Nix pins compiler, MPI, NetCDF, CMake, WRF, and WPS.
- Scientific configuration โ TOML case metadata plus native Fortran namelists.
- Execution backend โ local execution, Slurm, and site-specific MPI integration.
Hide the system complexity, not the scientific configuration.
Case configuration model¶
Every case uses case.toml for wrfkit-owned configuration. The native
namelist.wps and namelist.input remain visible and are still the exact
files consumed by WPS/WRF.
managed = true:wrfctl configoverlays only TOML-owned values; native keys omitted from TOML are preserved.managed = false: wrfkit never modifies either native namelist.case.tomlstill controls wrfkit-specific behavior such as forcing.
There is no separate YAML/native mode. Common settings get concise TOML tables, while every native option remains reachable through generic passthrough tables:
The renderer intentionally accepts arbitrary namelist groups and keys under
advanced.wps and advanced.wrf, so a new upstream option does not require
a wrfkit release before an advanced user can set it. advanced.wps_raw and
advanced.wrf_raw provide a verbatim right-hand-side escape hatch for unusual
Fortran syntax.
The annotated config/case.template.toml records current convenience fields,
meanings, example values, and documented upstream defaults/recommendations.
High-level orchestration and low-level primitives¶
wrfkit keeps the native WRF/WPS programs visible but does not require normal users to invoke every plumbing step manually.
The high-level contract is:
wrfctl plan --case NAME
-> show resolved scientific configuration and planned native stages
-> make no changes
wrfctl prep --case NAME
-> ensure reusable static geography
-> geogrid
-> ensure case forcing
-> stage Vtable / GRIBFILE links
-> ungrib
-> metgrid
-> stage WRF runtime data
-> real
-> verify wrfinput_d0* + wrfbdy_d01
-> write preparation manifest
wrfctl run --case NAME
-> compare current case with the preparation manifest
-> warn and continue for case/namelist changes
-> hard-stop for missing prep state or WRF/WPS version drift
-> wrf
-> require current SUCCESS COMPLETE WRF + new wrfout
The abstraction boundary is deliberate:
case.toml tells you WHAT experiment you are running.
namelist.* shows WHAT WRF/WPS actually receive.
wrfctl hides HOW the machine executes the workflow.
The existing fetch, prepare, and exec commands remain first-class
low-level primitives. They are useful for debugging, teaching, provenance, and
rerunning one stage without hiding the native workflow.
This orchestration reads case-owned configuration; it must not invent scientific
settings. case.toml carries acquisition/workflow metadata and optional
namelist overlays, while namelist.wps and namelist.input remain the
native execution files.
Automatic geography acquisition supports the two WPS mandatory package
profiles: low-resolution validation data and the highest-resolution mandatory
data. prep resolves the package from the case and never substitutes one
profile for another. User-provided geography remains an explicit external
profile.
Managed geography is extracted into a sibling temporary directory, checked for
the expected mandatory-field directories, given a provenance marker containing
the source URL and computed archive SHA-256, and renamed into place only after
validation. The downloader does not pre-scan large archives with tar -tzf;
the actual extraction is the single gzip/tar integrity pass, avoiding duplicate
full decompression before installation. It also reports download, SHA-256,
extraction, and total acquisition time. New archives live under the geography
data root so a shared data_root avoids one multi-gigabyte archive per
repository clone; an existing legacy low-resolution cache remains reusable.
Storage model¶
wrfkit distinguishes storage by lifetime rather than by a particular HPC filesystem layout.
Persistent state belongs with the project by default. This keeps the generic
workflow portable and avoids assuming that a machine provides /scratch or
/lscratch. The current default remains:
Persistent state includes installed WRF/WPS artifacts, configuration/provenance,
and other state that should survive normal temporary workspace cleanup.
Downloaded geography and forcing remain under project-local .wrfkit/data by
default, but a machine-level data_root may place those reusable inputs in a
shared location for use by multiple wrfkit clones. Case workspaces and scientific
outputs remain project-local.
Disposable state is high-I/O or reproducible temporary work that can be
recreated safely: build intermediates, staging areas, temporary links, and WPS
working files. A site profile may provide a scratch_root for this purpose.
Generic environments should prefer $TMPDIR when temporary storage is needed
rather than hard-coding /tmp.
The storage split is therefore conceptual:
flowchart LR
P["Persistent state"] --> PP["Project-local storage<br/>by default"]
D["Disposable state"] --> DS["Site/system scratch<br/>when configured"]
A site-specific scratch path must not leak into scientific case configuration.
Case, workspace, and log model¶
Tracked cases/<name> directories are configuration sources, not execution
directories. Native WPS/WRF programs run from a generated workspace:
The workspace contains symlinks to case-owned namelists, pinned WPS/WRF runtime tables, reusable forcing/geography, and generated WPS/WRF products. This keeps Git status focused on intentional case-configuration changes while preserving WRF's native file-based pipeline in one directory.
Execution logs are persistent provenance rather than disposable scratch data. wrfkit therefore snapshots native WPS/WRF diagnostic logs under:
A run directory records the invoked command, working directory, exit status,
Slurm job/step identifiers when available, a run-start marker, and one
native-logs.tar archive. WPS logs (geogrid.log*, metgrid.log*,
ungrib.log) and WRF RSL logs (rsl.out.*, rsl.error.*) remain in the
execution workspace and are archived only when modified by the current run.
This deliberately avoids many per-file rename operations on shared filesystems,
where metadata latency can dominate a short model run. Model products remain in
the generated workspace; scientific configuration remains under
cases/<name>.
Until wrfctl init establishes a first-class case manifest, wrfctl exec
accepts --case NAME, resolves configuration from cases/NAME, stages
.wrfkit/work/NAME, and executes from that workspace. Users can launch case
commands from the repository root while WPS/WRF still see their native relative
filenames. The selected case name is also exported as WRFKIT_CASE_NAME so
execution logs are routed to .wrfkit/logs/NAME.
Without --case, case identity continues to fall back to WRFKIT_CASE_NAME,
a cases/<name>/... current path, or the current working directory.
Repository-root executions without a selected case use the transitional name
default.
WPS build policy¶
wrfkit pins WPS 4.7.0 to release commit
5feccecd63384381b6942371c7a837f66e4ccb84. The WPS source is provided as a
non-flake Nix input and copied into project-local state before building.
The CMake workflow is used for both WRF and WPS. WPS is built only after a CMake-built WRF installation exists, with:
flowchart TB
WRF["WRF 4.8.0"] --> WPS["WPS 4.7.0"]
WPS --> G["geogrid"]
WPS --> U["ungrib"]
WPS --> M["metgrid"]
The initial WPS build enables MPI for geogrid/metgrid and enables GRIB2 by building the zlib/libpng/Jasper sources bundled with the pinned WPS release. This avoids coupling the build to whatever Jasper ABI a generic host happens to provide.
Build integration is separate from the future real-data workflow. The first
real-data milestone starts with a geogrid-only athens-minimal case. Its
low-resolution mandatory WPS geography is downloaded into persistent state at
.wrfkit/data/geog/low-res-mandatory; the source archive is cached separately
under .wrfkit/cache. This low-resolution dataset is a validation fixture, not
a production-data default.
The minimal case keeps its native namelist.wps tracked under
cases/athens-minimal and selects the lowres resolution defined by WPS
4.7.0's GEOGRID.TBL.ARW.
The first forcing path is GFS 0.25-degree data from NOAA/NCEP NOMADS. Case-level
forcing metadata is tracked in case.toml. wrfctl fetch gfs --case NAME
stores reusable GRIB2 files under the effective data root, while
wrfctl prepare gfs --case NAME creates Vtable and GRIBFILE.???
symlinks under .wrfkit/work/NAME without duplicating the forcing data.
Regional NOMADS subsets are keyed by the GFS product and requested bounding box, in addition to date/cycle. This prevents two cases with the same date and cycle but different spatial subsets from colliding in the cache.
The initial Athens minimal case deliberately uses a fixed short NOMADS window; because NOMADS is a rolling operational service, a durable archived forcing backend remains a future reproducibility improvement.
MPI policy¶
The initial WRF build enables MPI and uses Nix-provided OpenMPI. WPS
geogrid/metgrid are also built with MPI. Single-node MPI remains the first
validated execution target.
WRF/WPS source error semantics are not modified to protect a Slurm shell or batch job. Instead, execution isolation belongs to the scheduler backend. Single-node Slurm execution uses one bridge topology:
%%{init: {"flowchart": {"nodeSpacing": 20, "rankSpacing": 30}}}%%
flowchart LR
A["Single-node<br/>Slurm allocation"]
B["wrfctl child srun step<br/>1 task ยท N CPUs"]
C["Rootless Nix<br/>enter once"]
D["Pinned OpenMPI<br/>mpirun -np N"]
E["N WRF/WPS ranks"]
A --> B --> C --> D --> E
An interactive shell that already occupies an srun step adds --overlap
to the child step. A normal single-node sbatch allocation uses the same
topology without --overlap.
The child step uses --overlap so it can coexist with the step hosting the
interactive shell. Before creating it, wrfkit removes inherited
SLURM_CPU_BIND* variables: those bindings describe the parent step and can
refer to CPUs outside a smaller child allocation. Slurm then computes a fresh
binding/cpuset for the one child task.
A Slurm child step may be created outside the rootless-Nix mount namespace of
its parent. The child therefore re-enters wrfkit's Nix environment once. The
pinned OpenMPI mpirun then creates all N ranks inside that same namespace,
avoiding concurrent Nix evaluations and cross-namespace same-node MPI
transports. The child Slurm step still owns exactly N CPUs.
Because the bridge intentionally creates one Slurm task with N CPUs, the inner
OpenMPI process would otherwise see a one-slot Slurm allocation. Immediately
before mpirun, wrfkit hides the child step's Slurm resource-manager variables.
The kernel/Slurm cpuset remains in force, while OpenMPI discovers and binds
ranks within those allowed CPUs.
MPI execution remains launcher-based. The first-class launcher values are
auto, srun, mpirun, mpiexec, and custom. With auto, an
active Slurm allocation selects the Slurm backend; otherwise wrfkit uses the
OpenMPI mpirun provided by the pinned Nix environment.
The machine profile may set mpi_tasks=N for fixed non-Slurm servers or
mpi_tasks=auto. For Slurm batch jobs, auto uses SLURM_NTASKS. For an
interactive srun shell that reserves one task with multiple CPUs, auto may
use SLURM_CPUS_PER_TASK. In either single-node case, the bridge reserves N
CPUs before starting N MPI ranks. --ntasks N and --launcher NAME are
per-run overrides. Interactive and batch single-node Slurm execution are both validated.
High-level case execution adds a WRF-specific decomposition guard. It mirrors
WRF 4.8's MPASPECT factor selection and the native minimum-patch check:
for every domain, integer e_we / nproc_x and e_sn / nproc_y must both
be at least 10. Machine-level mpi_tasks=auto may therefore resolve to one
count for MPI-capable WPS stages and a smaller safe count for real/wrf.
Explicit nproc_x/nproc_y in the native namelist remain authoritative.
Multi-node rootless-Nix execution remains a separate validation target.
This is still a single-node-first policy. Multi-node support must additionally validate node topology, task placement, rootless-Nix visibility, PMIx/UCX, and site-specific fabric configuration without changing WRF/WPS source code.
The default Sapelo2 rootless-Nix store under /lscratch is node-local and
therefore cannot be assumed to exist on additional nodes. Multi-node execution
must use a shared store such as the sapelo2-shared profile, or realize an
equivalent environment independently on every participating node.
Generic multi-node MPI portability is not considered solved merely because WRF compiles with MPI. HPC execution can depend on Slurm, PMIx, UCX, InfiniBand/RDMA, and site-specific MPI configuration.
Sapelo2 storage¶
The default Sapelo2 profile uses a disposable node-local Nix store:
and declares the site scratch workspace:
The scratch root is for disposable high-I/O work. Persistent wrfkit state remains
project-local by default; wrfkit does not require the repository itself to live
on any particular Sapelo2 filesystem. Case workspaces currently remain
project-local because they contain scientific products that must survive across
job/node boundaries. A future scratch-backed workspace policy must first define
explicit persistence for outputs such as wrfout*; scratch_root is therefore
not used implicitly for case execution yet.
The optional shared rootless-Nix profile uses:
The node-local /lscratch paths must not be the basis for generic multi-node
MPI assumptions.
Milestones¶
- 0.1 WRF 4.8.0, GNU, NetCDF, OpenMPI build, ordinary Linux + Sapelo2 validation.
- 0.2 WPS 4.7.0 build integration and first real-data validation case.
- 0.3 TOML case configuration, namelist overlay/validation, provenance manifest.
- 0.4
wrfctl init, broader forcing/geography providers, richer cross-field validation. - 0.5+ Slurm backend, site profiles, multi-node MPI, forcing-data acquisition.
WRF real-data staging¶
The native pipeline remains explicit and inspectable:
metgrid -> real -> wrf. The normal high-level boundary groups
metgrid -> real under wrfctl prep, because preparation is complete only
when WRF initial and lateral-boundary files exist; wrfctl run then owns the
actual wrf.exe simulation.
wrfctl prepare wrf --case NAME still stages runtime tables and physics data
by symlinking files from the pinned WRF source tree's run/ directory into
.wrfkit/work/NAME. The scientific namelist.input remains case-owned and
is exposed to the workspace through a generated symlink.
After successful real, prep writes a small manifest containing a
preparation-relevant TOML fingerprint, native namelist hashes, and pinned
WRF/WPS versions. High-level run reports when case.toml or a native
namelist changed after prep, then continues with the existing prepared inputs.
A missing prep manifest or WRF/WPS version mismatch remains a hard error.
Advanced users can still invoke every native stage through wrfctl exec.